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,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
|
});
|
|
@@ -33,7 +55,7 @@ window.LoadToAgentAppFactories.createNavigationEventBindings = function createNa
|
|
|
33
55
|
state.guideExpanded = false;
|
|
34
56
|
saveGuideState();
|
|
35
57
|
renderSessions("guide");
|
|
36
|
-
$("#guideBtn").focus();
|
|
58
|
+
$("#guideBtn").focus({ preventScroll: true });
|
|
37
59
|
});
|
|
38
60
|
$("#beginnerGuide").addEventListener("click", (event) => {
|
|
39
61
|
const action = event.target.closest("[data-guide-action]")?.dataset.guideAction;
|
|
@@ -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,7 +107,23 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
18
107
|
renderProviderOverview();
|
|
19
108
|
renderSessions("filter");
|
|
20
109
|
announceProviderFilter();
|
|
21
|
-
|
|
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();
|
|
22
127
|
});
|
|
23
128
|
$("#sessionGrid").addEventListener("click", (event) => {
|
|
24
129
|
const card = event.target.closest("[data-session-id]");
|
|
@@ -34,7 +139,23 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
34
139
|
}
|
|
35
140
|
|
|
36
141
|
function bindLiveAgentEvents() {
|
|
37
|
-
$("#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
|
+
}
|
|
38
159
|
const tmuxPane = event.target.closest('.live-tmux-pane[data-tmux-type="pane"][data-tmux-id]');
|
|
39
160
|
const tmuxOverview = event.target.closest(".live-tmux-overview-open");
|
|
40
161
|
if (tmuxPane || tmuxOverview) {
|
|
@@ -133,6 +254,7 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
133
254
|
}
|
|
134
255
|
|
|
135
256
|
function bindGraphNavigationEvents() {
|
|
257
|
+
$("#openTmuxFromAgentWork").addEventListener("click", () => selectView("tmux", { focusMain: true }));
|
|
136
258
|
$("#graphBreadcrumbs").addEventListener("click", (event) => {
|
|
137
259
|
if (event.target.closest("[data-graph-reset]")) state.graphFocusId = null;
|
|
138
260
|
else {
|
|
@@ -150,6 +272,22 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
150
272
|
|
|
151
273
|
function bindTmuxMapEvents() {
|
|
152
274
|
$("#tmuxMap").addEventListener("click", (event) => {
|
|
275
|
+
const subagentToggle = event.target.closest("[data-tmux-subagents-toggle]");
|
|
276
|
+
if (subagentToggle) {
|
|
277
|
+
event.stopPropagation();
|
|
278
|
+
const paneId = subagentToggle.dataset.tmuxSubagentsToggle;
|
|
279
|
+
if (state.expandedTmuxSubagents.has(paneId)) state.expandedTmuxSubagents.delete(paneId);
|
|
280
|
+
else state.expandedTmuxSubagents.add(paneId);
|
|
281
|
+
renderTmuxMap();
|
|
282
|
+
requestAnimationFrame(() => $("#tmuxMap").querySelector(`[data-tmux-subagents-toggle="${CSS.escape(paneId)}"]`)?.focus({ preventScroll: true }));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const subagentChat = event.target.closest("[data-open-subagent-chat]");
|
|
286
|
+
if (subagentChat) {
|
|
287
|
+
event.stopPropagation();
|
|
288
|
+
openSubagentConversation(subagentChat.dataset.openSubagentChat);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
153
291
|
const control = event.target.closest("[data-control-tmux]");
|
|
154
292
|
if (control) {
|
|
155
293
|
event.stopPropagation();
|
|
@@ -165,10 +303,25 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
165
303
|
}
|
|
166
304
|
const node = event.target.closest("[data-tmux-type][data-tmux-id]");
|
|
167
305
|
if (!node) return;
|
|
168
|
-
|
|
306
|
+
const nextFocus = { type: node.dataset.tmuxType, id: node.dataset.tmuxId };
|
|
307
|
+
state.tmuxFocus = nextFocus;
|
|
169
308
|
renderTmuxMap();
|
|
309
|
+
requestAnimationFrame(() => $("#tmuxMap")?.querySelector(`[data-tmux-type="${CSS.escape(nextFocus.type)}"][data-tmux-id="${CSS.escape(nextFocus.id)}"]`)?.focus({ preventScroll: true }));
|
|
170
310
|
if (node.dataset.tmuxType === "pane") window.LoadToAgentTerminal?.selectTmuxById(node.dataset.tmuxId);
|
|
171
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
|
+
});
|
|
172
325
|
$("#tmuxBreadcrumbs").addEventListener("click", (event) => {
|
|
173
326
|
if (event.target.closest("[data-tmux-reset]")) state.tmuxFocus = null;
|
|
174
327
|
else {
|
|
@@ -178,6 +331,19 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
178
331
|
}
|
|
179
332
|
renderTmuxMap();
|
|
180
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
|
+
});
|
|
181
347
|
$("#tmuxResetBtn").addEventListener("click", () => {
|
|
182
348
|
state.tmuxFocus = null;
|
|
183
349
|
renderTmuxMap();
|
|
@@ -185,6 +351,7 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
185
351
|
}
|
|
186
352
|
|
|
187
353
|
function bindSessionAndAgentEvents() {
|
|
354
|
+
bindManagementEvents();
|
|
188
355
|
bindSessionListEvents();
|
|
189
356
|
bindLiveAgentEvents();
|
|
190
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 };
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createGraphModel = function createGraphModel(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const {
|
|
7
8
|
$,
|
|
8
9
|
esc,
|
|
@@ -78,10 +79,10 @@ window.LoadToAgentAppFactories.createGraphModel = function createGraphModel(cont
|
|
|
78
79
|
if (tmux)
|
|
79
80
|
return {
|
|
80
81
|
kind: "tmux",
|
|
81
|
-
label: "
|
|
82
|
-
detail: [tmux.distro, tmux.sessionName, tmux.paneNativeId || tmux.paneId].filter(Boolean).join(" · ") || "
|
|
82
|
+
label: t("graph.tmux_used"),
|
|
83
|
+
detail: [tmux.distro, tmux.sessionName, tmux.paneNativeId || tmux.paneId].filter(Boolean).join(" · ") || t("graph.running_in_split_terminal"),
|
|
83
84
|
};
|
|
84
|
-
return { kind: "standard", label: "
|
|
85
|
+
return { kind: "standard", label: t("graph.standard_run"), detail: t("graph.tmux_not_used") };
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
function executionModeBadge(session, compact = false) {
|
|
@@ -103,5 +104,32 @@ window.LoadToAgentAppFactories.createGraphModel = function createGraphModel(cont
|
|
|
103
104
|
return seen.size;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
function runtimeAgentSummary(model, tmuxEntries = []) {
|
|
108
|
+
const liveNodes = model.nodes.filter(isLiveSession);
|
|
109
|
+
const liveById = new Map(liveNodes.map((session) => [session.id, session]));
|
|
110
|
+
const tmuxSessionIds = new Set(
|
|
111
|
+
tmuxEntries
|
|
112
|
+
.filter((entry) => entry && entry.pane && !entry.pane.dead)
|
|
113
|
+
.map((entry) => String(entry.agent && entry.agent.linkedSessionId || ""))
|
|
114
|
+
.filter((id) => id && liveById.has(id)),
|
|
115
|
+
);
|
|
116
|
+
for (const session of liveNodes) {
|
|
117
|
+
if (agentExecutionMode(session).kind === "tmux") tmuxSessionIds.add(session.id);
|
|
118
|
+
}
|
|
119
|
+
const tmuxCount = liveNodes.filter((session) => tmuxSessionIds.has(session.id)).length;
|
|
120
|
+
const runningExecutions = liveNodes.flatMap((session) => (session.executions || []).filter((activity) => activity.status === "running"));
|
|
121
|
+
return {
|
|
122
|
+
activeCount: liveNodes.length,
|
|
123
|
+
standardCount: liveNodes.length - tmuxCount,
|
|
124
|
+
tmuxCount,
|
|
125
|
+
rootCount: liveNodes.filter((session) => !session.parentId).length,
|
|
126
|
+
activeHelperCount: liveNodes.filter((session) => session.parentId).length,
|
|
127
|
+
helperRecordCount: model.nodes.filter((session) => session.parentId).length,
|
|
128
|
+
runningExecutionCount: runningExecutions.length,
|
|
129
|
+
shellExecutionCount: runningExecutions.filter((activity) => activity.kind === "shell").length,
|
|
130
|
+
backgroundExecutionCount: runningExecutions.filter((activity) => activity.mode === "background" || activity.kind === "background").length,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { graphPath, connectedGraphSessions, sortGraphNodes, graphChildren, agentExecutionMode, executionModeBadge, graphDescendantCount, runtimeAgentSummary };
|
|
107
135
|
};
|
|
@@ -13,12 +13,13 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
13
13
|
graphPath,
|
|
14
14
|
connectedGraphSessions,
|
|
15
15
|
sortGraphNodes,
|
|
16
|
-
|
|
16
|
+
runtimeAgentSummary,
|
|
17
17
|
liveTmuxEntries,
|
|
18
18
|
runtimeSeparatedOverview,
|
|
19
19
|
focusedGraph,
|
|
20
20
|
scheduleAgentWorkflowConnections,
|
|
21
21
|
} = context;
|
|
22
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
22
23
|
|
|
23
24
|
function renderAgentMap(sessions, motionKind = "refresh") {
|
|
24
25
|
const model = connectedGraphSessions(sessions);
|
|
@@ -36,7 +37,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
36
37
|
if (focus) {
|
|
37
38
|
$("#liveSessionGrid").innerHTML = focusedGraph(focus, model, motionKind);
|
|
38
39
|
const path = graphPath(focus, model.byId);
|
|
39
|
-
$("#graphBreadcrumbs").innerHTML = `<button type="button" data-graph-reset
|
|
40
|
+
$("#graphBreadcrumbs").innerHTML = `<button type="button" data-graph-reset>${esc(t("graph.task_list"))}</button>${path
|
|
40
41
|
.map((item) => {
|
|
41
42
|
const label = item.parentId ? item.agentName || agentRoleLabel(item.agentRole) : item.title;
|
|
42
43
|
const preview = readablePreview(label, item.parentId ? 42 : 72);
|
|
@@ -49,27 +50,29 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
49
50
|
$("#graphResetBtn").classList.remove("hidden");
|
|
50
51
|
scheduleAgentWorkflowConnections();
|
|
51
52
|
} else {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const activeCount = model.nodes.filter(isLiveSession).length + tmuxEntries.filter((entry) => !entry.agent.linkedSessionId).length;
|
|
55
|
-
$("#liveSessionGrid").innerHTML = `<details class="runtime-disclosure" open>
|
|
53
|
+
const runtime = runtimeAgentSummary(model, liveTmuxEntries(state.snapshot && state.snapshot.tmux));
|
|
54
|
+
$("#liveSessionGrid").innerHTML = `<details class="runtime-disclosure" data-disclosure-key="home:runtime-overview" open>
|
|
56
55
|
<summary>
|
|
57
56
|
<span>
|
|
58
|
-
<b>${
|
|
59
|
-
<small
|
|
57
|
+
<b>${esc(t("graph.ai_working_count", { count: runtime.activeCount }))}</b>
|
|
58
|
+
<small>${esc(t("graph.runtime_count_explanation", { standard: runtime.standardCount, tmux: runtime.tmuxCount, helpers: runtime.activeHelperCount }))}${runtime.runningExecutionCount ? ` · ${esc(t("graph.runtime_execution_count", { shell: runtime.shellExecutionCount, background: runtime.backgroundExecutionCount }))}` : ""}</small>
|
|
60
59
|
</span>
|
|
61
|
-
<em
|
|
60
|
+
<em>${esc(t("graph.view_detailed_flow"))} <i aria-hidden="true">↓</i>
|
|
62
61
|
</em>
|
|
63
62
|
</summary>${runtimeSeparatedOverview(roots, model)}</details>`;
|
|
64
63
|
$("#graphBreadcrumbs").innerHTML =
|
|
65
64
|
`<span class="map-hint">
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
${esc(t("graph.standard_ai"))} <b>${runtime.standardCount}</b> ·
|
|
66
|
+
${esc(t("graph.tmux_ai"))} <b>${runtime.tmuxCount}</b> ·
|
|
67
|
+
${esc(t("graph.active_helper_ai"))} <b>${runtime.activeHelperCount}</b> ·
|
|
68
|
+
${esc(t("graph.helper_ai_history"))} <b>${runtime.helperRecordCount}</b> ·
|
|
69
|
+
${esc(t("graph.running_shell"))} <b>${runtime.shellExecutionCount}</b> ·
|
|
70
|
+
${esc(t("graph.running_background"))} <b>${runtime.backgroundExecutionCount}</b>
|
|
69
71
|
</span>`;
|
|
70
72
|
$("#graphResetBtn").classList.add("hidden");
|
|
73
|
+
return runtime.activeCount;
|
|
71
74
|
}
|
|
72
|
-
return model.nodes.filter(isLiveSession).length
|
|
75
|
+
return model.nodes.filter(isLiveSession).length;
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
return { renderAgentMap };
|