loadtoagent 1.3.4 → 1.3.6
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/docs/PROVIDER-CONTRACTS.md +3 -1
- package/package.json +4 -2
- package/renderer/app-dashboard.js +75 -13
- package/renderer/app-drawer-content.js +189 -8
- package/renderer/app-drawer.js +4 -3
- package/renderer/app-events-dialogs.js +6 -1
- package/renderer/app-events-filters.js +80 -2
- package/renderer/app-events-sessions.js +110 -17
- package/renderer/app-graph-model.js +8 -5
- package/renderer/app-graph-orchestration.js +43 -11
- package/renderer/app-graph-view.js +75 -14
- package/renderer/app-quality.js +2 -0
- package/renderer/app-session-render.js +7 -7
- package/renderer/app.js +104 -0
- package/renderer/i18n-messages.js +46 -9
- package/renderer/index.html +33 -7
- package/renderer/styles-components.css +218 -1
- package/renderer/styles-control-room.css +820 -29
- package/renderer/styles-readability.css +1 -9
- package/renderer/styles-responsive-shell.css +22 -1
- package/renderer/styles-terminal.css +21 -31
- package/renderer/styles-workflow-map.css +8 -0
- package/renderer/terminal-events.js +0 -13
- package/renderer/terminal-workbench.js +1 -4
- package/src/agentMonitor/claudeParser.js +189 -4
- package/src/agentMonitor/codexCollaboration.js +1 -0
- package/src/agentMonitor/codexParser.js +6 -5
- package/src/agentMonitor/executionActivity.js +26 -1
- package/src/agentMonitor/hierarchy.js +6 -3
- package/src/agentMonitor.js +10 -3
- package/src/contracts.js +1 -1
- package/src/monitorWorker.js +2 -0
- package/src/nodePtyRuntime.js +58 -0
- package/src/terminalHost.js +104 -5
- package/src/terminalManager.js +5 -1
|
@@ -9,7 +9,7 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
9
9
|
function bindFilterAndWorkspaceEvents() {
|
|
10
10
|
const syncFilterResetButton = () => {
|
|
11
11
|
const hasFilters = Boolean(
|
|
12
|
-
$("#searchInput").value || state.search || state.providerFilters.size || state.workspace !== "all" || state.sort !== "recent",
|
|
12
|
+
$("#searchInput").value || state.search || state.providerFilters.size || state.workspace !== "all" || state.sort !== "recent" || state.controlRoomSort !== "recent",
|
|
13
13
|
);
|
|
14
14
|
$("#resetFiltersBtn").classList.toggle("hidden", !hasFilters);
|
|
15
15
|
};
|
|
@@ -62,6 +62,7 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
62
62
|
if (item) {
|
|
63
63
|
const label = item.querySelector("strong")?.textContent.trim() || t("project.all");
|
|
64
64
|
state.workspace = item.dataset.workspace;
|
|
65
|
+
state.controlRoomPage = 0;
|
|
65
66
|
state.visibleLimit = 30;
|
|
66
67
|
renderWorkspaces();
|
|
67
68
|
renderSessions("filter");
|
|
@@ -85,9 +86,80 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
85
86
|
workspaceLists.forEach((list) => {
|
|
86
87
|
list.addEventListener("click", handleWorkspaceClick);
|
|
87
88
|
list.addEventListener("keydown", (event) => {
|
|
88
|
-
|
|
89
|
+
const horizontal = event.currentTarget.id === "workspaceList";
|
|
90
|
+
moveFocus(event, event.currentTarget, "[data-workspace]", horizontal ? ["ArrowLeft", "ArrowUp"] : ["ArrowUp"], horizontal ? ["ArrowRight", "ArrowDown"] : ["ArrowDown"]);
|
|
89
91
|
});
|
|
90
92
|
});
|
|
93
|
+
const controlProjectSelect = $("#controlRoomProjectSelect");
|
|
94
|
+
controlProjectSelect?.addEventListener("change", (event) => {
|
|
95
|
+
state.workspace = event.target.value;
|
|
96
|
+
state.controlRoomPage = 0;
|
|
97
|
+
state.visibleLimit = 30;
|
|
98
|
+
renderWorkspaces();
|
|
99
|
+
renderSessions("filter");
|
|
100
|
+
syncFilterResetButton();
|
|
101
|
+
saveDashboardPreferences();
|
|
102
|
+
announce(t("filter.workspace_results", { project: event.target.selectedOptions[0]?.textContent || t("control.all_projects"), count: filteredSessions().length }));
|
|
103
|
+
});
|
|
104
|
+
const controlSortSelect = $("#controlRoomSortSelect");
|
|
105
|
+
controlSortSelect?.addEventListener("change", (event) => {
|
|
106
|
+
state.controlRoomSort = event.target.value;
|
|
107
|
+
state.controlRoomPage = 0;
|
|
108
|
+
state.visibleLimit = 30;
|
|
109
|
+
renderSessions("filter");
|
|
110
|
+
syncFilterResetButton();
|
|
111
|
+
saveDashboardPreferences();
|
|
112
|
+
announce(t("filter.sort_changed", { sort: event.target.selectedOptions[0]?.textContent || event.target.value, count: filteredSessions().length }));
|
|
113
|
+
});
|
|
114
|
+
const controlSearch = $("#controlRoomSearch");
|
|
115
|
+
const controlSearchInput = $("#controlRoomSearchInput");
|
|
116
|
+
const controlSearchButton = $("#controlRoomSearchBtn");
|
|
117
|
+
let controlSearchTimer = null;
|
|
118
|
+
const setControlSearchOpen = (open) => {
|
|
119
|
+
controlSearch?.classList.toggle("is-open", open);
|
|
120
|
+
controlSearchButton?.setAttribute("aria-expanded", open ? "true" : "false");
|
|
121
|
+
if (controlSearchInput) {
|
|
122
|
+
controlSearchInput.tabIndex = open ? 0 : -1;
|
|
123
|
+
controlSearchInput.setAttribute("aria-hidden", open ? "false" : "true");
|
|
124
|
+
}
|
|
125
|
+
if (open) requestAnimationFrame(() => controlSearchInput?.focus());
|
|
126
|
+
};
|
|
127
|
+
controlSearchButton?.addEventListener("click", () => setControlSearchOpen(!controlSearch?.classList.contains("is-open")));
|
|
128
|
+
controlSearchInput?.addEventListener("input", (event) => {
|
|
129
|
+
clearTimeout(controlSearchTimer);
|
|
130
|
+
const value = event.target.value;
|
|
131
|
+
if ($("#searchInput")) $("#searchInput").value = value;
|
|
132
|
+
controlSearchTimer = setTimeout(() => {
|
|
133
|
+
state.search = normalizedSearch(value);
|
|
134
|
+
state.controlRoomPage = 0;
|
|
135
|
+
state.visibleLimit = 30;
|
|
136
|
+
renderSessions("filter");
|
|
137
|
+
syncFilterResetButton();
|
|
138
|
+
saveDashboardPreferences();
|
|
139
|
+
announce(t("filter.search_results", { count: filteredSessions().length }));
|
|
140
|
+
}, 120);
|
|
141
|
+
});
|
|
142
|
+
controlSearchInput?.addEventListener("keydown", (event) => {
|
|
143
|
+
if (event.key !== "Escape") return;
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
if (event.currentTarget.value) {
|
|
146
|
+
event.currentTarget.value = "";
|
|
147
|
+
event.currentTarget.dispatchEvent(new Event("input", { bubbles: true }));
|
|
148
|
+
} else {
|
|
149
|
+
setControlSearchOpen(false);
|
|
150
|
+
controlSearchButton?.focus();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
$("#controlRoomPagePrev")?.addEventListener("click", (event) => {
|
|
154
|
+
state.controlRoomPage = Math.max(0, Number(state.controlRoomPage || 0) - 1);
|
|
155
|
+
renderSessions("filter");
|
|
156
|
+
event.currentTarget.focus({ preventScroll: true });
|
|
157
|
+
});
|
|
158
|
+
$("#controlRoomPageNext")?.addEventListener("click", (event) => {
|
|
159
|
+
state.controlRoomPage = Math.max(0, Number(state.controlRoomPage || 0) + 1);
|
|
160
|
+
renderSessions("filter");
|
|
161
|
+
event.currentTarget.focus({ preventScroll: true });
|
|
162
|
+
});
|
|
91
163
|
let searchTimer = null;
|
|
92
164
|
$("#searchInput").addEventListener("input", (event) => {
|
|
93
165
|
clearTimeout(searchTimer);
|
|
@@ -96,6 +168,7 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
96
168
|
syncFilterResetButton();
|
|
97
169
|
searchTimer = setTimeout(() => {
|
|
98
170
|
state.search = normalizedSearch(value);
|
|
171
|
+
state.controlRoomPage = 0;
|
|
99
172
|
state.visibleLimit = 30;
|
|
100
173
|
renderSessions("filter");
|
|
101
174
|
announce(window.LoadToAgentI18n.t("filter.search_results", { count: filteredSessions().length }));
|
|
@@ -108,6 +181,7 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
108
181
|
$("#searchInput").value = "";
|
|
109
182
|
$("#searchClearBtn").classList.add("hidden");
|
|
110
183
|
state.search = "";
|
|
184
|
+
state.controlRoomPage = 0;
|
|
111
185
|
state.visibleLimit = 30;
|
|
112
186
|
renderSessions("filter");
|
|
113
187
|
announce(window.LoadToAgentI18n.t("filter.search_cleared"));
|
|
@@ -138,6 +212,7 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
138
212
|
const chip = event.target.closest("[data-provider-filter]");
|
|
139
213
|
if (!chip) return;
|
|
140
214
|
toggleProviderFilter(chip.dataset.providerFilter);
|
|
215
|
+
state.controlRoomPage = 0;
|
|
141
216
|
state.visibleLimit = 30;
|
|
142
217
|
renderProviderFilter();
|
|
143
218
|
renderProviderOverview();
|
|
@@ -154,6 +229,7 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
154
229
|
});
|
|
155
230
|
$("#sortSelect").addEventListener("change", (event) => {
|
|
156
231
|
state.sort = event.target.value;
|
|
232
|
+
state.controlRoomPage = 0;
|
|
157
233
|
state.visibleLimit = 30;
|
|
158
234
|
renderSessions("filter");
|
|
159
235
|
const label = event.target.selectedOptions[0]?.textContent || event.target.value;
|
|
@@ -167,6 +243,8 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
167
243
|
state.providerFilters.clear();
|
|
168
244
|
state.workspace = "all";
|
|
169
245
|
state.sort = "recent";
|
|
246
|
+
state.controlRoomSort = "recent";
|
|
247
|
+
state.controlRoomPage = 0;
|
|
170
248
|
state.visibleLimit = 30;
|
|
171
249
|
$("#searchInput").value = "";
|
|
172
250
|
$("#searchClearBtn").classList.add("hidden");
|
|
@@ -10,21 +10,112 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
10
10
|
copyText = async () => false,
|
|
11
11
|
announce = () => {},
|
|
12
12
|
moveSessionOrder = () => false,
|
|
13
|
+
archiveSession = () => false,
|
|
13
14
|
} = context;
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
let sessionDragJustEnded = false;
|
|
17
|
+
|
|
18
|
+
const sortableSessionId = node => String(node?.dataset.sessionSortable || "");
|
|
19
|
+
const clearSessionDropState = container => {
|
|
20
|
+
container.querySelectorAll("[data-session-sortable]").forEach(node => {
|
|
21
|
+
node.classList.remove("session-sort-dragging");
|
|
22
|
+
node.removeAttribute("data-session-drop-edge");
|
|
23
|
+
node.setAttribute("aria-grabbed", "false");
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
const sessionDropPlacement = (target, event) => {
|
|
27
|
+
const bounds = target.getBoundingClientRect();
|
|
28
|
+
const layout = window.getComputedStyle(target.parentElement || target);
|
|
29
|
+
const columns = String(layout.gridTemplateColumns || "none").trim().split(/\s+/).filter(value => value && value !== "none");
|
|
30
|
+
const horizontal = columns.length > 1;
|
|
31
|
+
const placeAfter = horizontal
|
|
32
|
+
? event.clientX > bounds.left + bounds.width / 2
|
|
33
|
+
: event.clientY > bounds.top + bounds.height / 2;
|
|
34
|
+
return {
|
|
35
|
+
placeAfter,
|
|
36
|
+
edge: horizontal ? (placeAfter ? "right" : "left") : (placeAfter ? "bottom" : "top"),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const commitSessionPosition = (container, sourceId, targetId, placeAfter, focusSource = false) => {
|
|
40
|
+
if (!moveSessionOrder(sourceId, targetId, placeAfter)) return false;
|
|
22
41
|
state.sort = "recent";
|
|
42
|
+
state.controlRoomSort = "recent";
|
|
23
43
|
if ($("#sortSelect")) $("#sortSelect").value = "recent";
|
|
44
|
+
if ($("#controlRoomSortSelect")) $("#controlRoomSortSelect").value = "recent";
|
|
24
45
|
saveDashboardPreferences();
|
|
25
46
|
renderSessions("reorder");
|
|
26
47
|
announce(window.LoadToAgentI18n.t("session.position_changed"));
|
|
27
|
-
requestAnimationFrame(() =>
|
|
48
|
+
if (focusSource) requestAnimationFrame(() => container.querySelector(`[data-session-sortable="${CSS.escape(sourceId)}"]`)?.focus({ preventScroll: true }));
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
51
|
+
const bindSortableSessionList = (container, selector) => {
|
|
52
|
+
let draggedSessionId = "";
|
|
53
|
+
const finishDrag = () => {
|
|
54
|
+
clearSessionDropState(container);
|
|
55
|
+
draggedSessionId = "";
|
|
56
|
+
sessionDragJustEnded = true;
|
|
57
|
+
setTimeout(() => { sessionDragJustEnded = false; }, 0);
|
|
58
|
+
};
|
|
59
|
+
container.addEventListener("dragstart", (event) => {
|
|
60
|
+
const item = event.target.closest(selector);
|
|
61
|
+
if (!item) return;
|
|
62
|
+
if (event.target !== item && event.target.closest("button, a, input, select, textarea, summary, [contenteditable='true']")) {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
draggedSessionId = sortableSessionId(item);
|
|
67
|
+
if (!draggedSessionId) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
item.classList.add("session-sort-dragging");
|
|
72
|
+
item.setAttribute("aria-grabbed", "true");
|
|
73
|
+
if (event.dataTransfer) {
|
|
74
|
+
event.dataTransfer.effectAllowed = "move";
|
|
75
|
+
event.dataTransfer.setData("text/plain", draggedSessionId);
|
|
76
|
+
event.dataTransfer.setData("application/x-loadtoagent-session-list", container.id);
|
|
77
|
+
const dragImage = item.querySelector(":scope > header, .card-head") || item;
|
|
78
|
+
event.dataTransfer.setDragImage(dragImage, 20, 20);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
container.addEventListener("dragover", (event) => {
|
|
82
|
+
const target = event.target.closest(selector);
|
|
83
|
+
const sourceId = draggedSessionId || event.dataTransfer?.getData("text/plain");
|
|
84
|
+
const sourceList = event.dataTransfer?.getData("application/x-loadtoagent-session-list");
|
|
85
|
+
if (!target || !sourceId || (!draggedSessionId && sourceList !== container.id) || sortableSessionId(target) === sourceId) return;
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
88
|
+
container.querySelectorAll("[data-session-drop-edge]").forEach(node => node.removeAttribute("data-session-drop-edge"));
|
|
89
|
+
target.dataset.sessionDropEdge = sessionDropPlacement(target, event).edge;
|
|
90
|
+
});
|
|
91
|
+
container.addEventListener("drop", (event) => {
|
|
92
|
+
const target = event.target.closest(selector);
|
|
93
|
+
const sourceId = draggedSessionId || event.dataTransfer?.getData("text/plain");
|
|
94
|
+
const sourceList = event.dataTransfer?.getData("application/x-loadtoagent-session-list");
|
|
95
|
+
if (!target || !sourceId || (!draggedSessionId && sourceList !== container.id) || sortableSessionId(target) === sourceId) return;
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
event.stopPropagation();
|
|
98
|
+
const placement = sessionDropPlacement(target, event);
|
|
99
|
+
const changed = commitSessionPosition(container, sourceId, sortableSessionId(target), placement.placeAfter);
|
|
100
|
+
finishDrag();
|
|
101
|
+
if (!changed) clearSessionDropState(container);
|
|
102
|
+
});
|
|
103
|
+
container.addEventListener("dragend", finishDrag);
|
|
104
|
+
container.addEventListener("dragleave", (event) => {
|
|
105
|
+
if (!container.contains(event.relatedTarget)) clearSessionDropState(container);
|
|
106
|
+
});
|
|
107
|
+
container.addEventListener("keydown", (event) => {
|
|
108
|
+
const item = event.target.closest(selector);
|
|
109
|
+
if (!item || event.target !== item || !event.altKey || !["ArrowUp", "ArrowDown"].includes(event.key)) return;
|
|
110
|
+
const nodes = Array.from(container.querySelectorAll(selector));
|
|
111
|
+
const current = nodes.indexOf(item);
|
|
112
|
+
const offset = event.key === "ArrowUp" ? -1 : 1;
|
|
113
|
+
const target = nodes[current + offset];
|
|
114
|
+
if (current < 0 || !target) return;
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
event.stopPropagation();
|
|
117
|
+
commitSessionPosition(container, sortableSessionId(item), sortableSessionId(target), offset > 0, true);
|
|
118
|
+
});
|
|
28
119
|
};
|
|
29
120
|
|
|
30
121
|
const managementFilterLabel = value => value === "all" ? window.LoadToAgentI18n.t("management.filter_all") : window.LoadToAgentI18n.t(`management.health.${value}`);
|
|
@@ -172,6 +263,7 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
172
263
|
}
|
|
173
264
|
|
|
174
265
|
function bindSessionListEvents() {
|
|
266
|
+
bindSortableSessionList($("#sessionGrid"), "[data-session-id][data-session-sortable]");
|
|
175
267
|
$("#automationOverview").addEventListener("click", (event) => {
|
|
176
268
|
const loopSelect = event.target.closest("[data-loop-select]");
|
|
177
269
|
if (loopSelect) {
|
|
@@ -228,12 +320,7 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
228
320
|
cards[next].focus();
|
|
229
321
|
});
|
|
230
322
|
$("#sessionGrid").addEventListener("click", (event) => {
|
|
231
|
-
|
|
232
|
-
if (order) {
|
|
233
|
-
event.stopPropagation();
|
|
234
|
-
moveVisibleSession(order, $("#sessionGrid"), "[data-session-id]");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
323
|
+
if (sessionDragJustEnded) return;
|
|
237
324
|
const card = event.target.closest("[data-session-id]");
|
|
238
325
|
if (card) openDrawer(card.dataset.sessionId);
|
|
239
326
|
});
|
|
@@ -248,11 +335,17 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
248
335
|
}
|
|
249
336
|
|
|
250
337
|
function bindLiveAgentEvents() {
|
|
338
|
+
bindSortableSessionList($("#liveSessionGrid"), "[data-control-session][data-session-sortable]");
|
|
251
339
|
$("#liveSessionGrid").addEventListener("click", async (event) => {
|
|
252
|
-
|
|
253
|
-
|
|
340
|
+
if (sessionDragJustEnded) return;
|
|
341
|
+
const archive = event.target.closest("[data-session-archive]");
|
|
342
|
+
if (archive) {
|
|
254
343
|
event.stopPropagation();
|
|
255
|
-
|
|
344
|
+
if (archiveSession(archive.dataset.sessionArchive)) {
|
|
345
|
+
if (state.graphFocusId === archive.dataset.sessionArchive) state.graphFocusId = null;
|
|
346
|
+
renderSessions("archive");
|
|
347
|
+
announce(window.LoadToAgentI18n.t("control.moved_to_history"));
|
|
348
|
+
}
|
|
256
349
|
return;
|
|
257
350
|
}
|
|
258
351
|
const route = event.target.closest("[data-agent-command-route]");
|
|
@@ -10,6 +10,7 @@ window.LoadToAgentAppFactories.createGraphModel = function createGraphModel(cont
|
|
|
10
10
|
state,
|
|
11
11
|
compact,
|
|
12
12
|
isLiveSession,
|
|
13
|
+
isControlRoomSession = isLiveSession,
|
|
13
14
|
stableSessionSort = sessions => [...sessions],
|
|
14
15
|
} = context;
|
|
15
16
|
|
|
@@ -27,7 +28,7 @@ window.LoadToAgentAppFactories.createGraphModel = function createGraphModel(cont
|
|
|
27
28
|
|
|
28
29
|
function connectedGraphSessions(sessions, focusId = state.graphFocusId) {
|
|
29
30
|
const byId = new Map(sessions.map((session) => [session.id, session]));
|
|
30
|
-
const included = new Set(sessions.filter(
|
|
31
|
+
const included = new Set(sessions.filter(isControlRoomSession).map((session) => session.id));
|
|
31
32
|
const includeAncestors = (session) => {
|
|
32
33
|
let current = session;
|
|
33
34
|
const seen = new Set();
|
|
@@ -37,7 +38,7 @@ window.LoadToAgentAppFactories.createGraphModel = function createGraphModel(cont
|
|
|
37
38
|
current = current.parentId ? byId.get(current.parentId) : null;
|
|
38
39
|
}
|
|
39
40
|
};
|
|
40
|
-
sessions.filter(
|
|
41
|
+
sessions.filter(isControlRoomSession).forEach(includeAncestors);
|
|
41
42
|
const includeDescendants = (session) => {
|
|
42
43
|
const queue = [...((session && session.childIds) || [])];
|
|
43
44
|
const seen = new Set();
|
|
@@ -102,6 +103,7 @@ window.LoadToAgentAppFactories.createGraphModel = function createGraphModel(cont
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
function runtimeAgentSummary(model, tmuxEntries = []) {
|
|
106
|
+
const controlNodes = model.nodes.filter(isControlRoomSession);
|
|
105
107
|
const liveNodes = model.nodes.filter(isLiveSession);
|
|
106
108
|
const liveById = new Map(liveNodes.map((session) => [session.id, session]));
|
|
107
109
|
const tmuxSessionIds = new Set(
|
|
@@ -114,12 +116,13 @@ window.LoadToAgentAppFactories.createGraphModel = function createGraphModel(cont
|
|
|
114
116
|
if (agentExecutionMode(session).kind === "tmux") tmuxSessionIds.add(session.id);
|
|
115
117
|
}
|
|
116
118
|
const tmuxCount = liveNodes.filter((session) => tmuxSessionIds.has(session.id)).length;
|
|
117
|
-
const runningExecutions =
|
|
119
|
+
const runningExecutions = controlNodes.flatMap((session) => (session.executions || []).filter((activity) => activity.status === "running"));
|
|
118
120
|
return {
|
|
119
|
-
activeCount:
|
|
121
|
+
activeCount: controlNodes.length,
|
|
122
|
+
waitingCount: controlNodes.filter((session) => !isLiveSession(session)).length,
|
|
120
123
|
standardCount: liveNodes.length - tmuxCount,
|
|
121
124
|
tmuxCount,
|
|
122
|
-
rootCount:
|
|
125
|
+
rootCount: controlNodes.filter((session) => !session.parentId).length,
|
|
123
126
|
activeHelperCount: liveNodes.filter((session) => session.parentId).length,
|
|
124
127
|
helperRecordCount: model.nodes.filter((session) => session.parentId).length,
|
|
125
128
|
runningExecutionCount: runningExecutions.length,
|
|
@@ -10,32 +10,52 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
10
10
|
readablePreview,
|
|
11
11
|
agentRoleLabel,
|
|
12
12
|
isLiveSession,
|
|
13
|
+
isControlRoomSession = isLiveSession,
|
|
13
14
|
graphPath,
|
|
14
15
|
connectedGraphSessions,
|
|
15
16
|
sortGraphNodes,
|
|
17
|
+
stableSessionSort = sessions => [...sessions],
|
|
16
18
|
runtimeAgentSummary,
|
|
17
19
|
liveTmuxEntries,
|
|
18
20
|
runtimeSeparatedOverview,
|
|
19
21
|
focusedGraph,
|
|
20
22
|
scheduleAgentWorkflowConnections,
|
|
23
|
+
rememberDisclosureStates = () => {},
|
|
24
|
+
restoreDisclosureStates = () => {},
|
|
21
25
|
} = context;
|
|
22
26
|
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
23
27
|
|
|
24
28
|
function renderAgentMap(sessions, motionKind = "refresh") {
|
|
29
|
+
const liveSessionGrid = $("#liveSessionGrid");
|
|
30
|
+
rememberDisclosureStates(liveSessionGrid);
|
|
25
31
|
const model = connectedGraphSessions(sessions);
|
|
26
32
|
const focus =
|
|
27
33
|
state.graphFocusId && model.byId.get(state.graphFocusId) && model.included.has(state.graphFocusId) ? model.byId.get(state.graphFocusId) : null;
|
|
28
34
|
if (state.graphFocusId && !focus) state.graphFocusId = null;
|
|
29
|
-
const
|
|
35
|
+
const rootSessions = model.nodes.filter((session) => !session.parentId || !model.included.has(session.parentId));
|
|
36
|
+
const roots = state.controlRoomSort === "tokens"
|
|
37
|
+
? [...rootSessions].sort((a, b) => Number((b.usage && b.usage.total) || 0) - Number((a.usage && a.usage.total) || 0))
|
|
38
|
+
: state.controlRoomSort === "context"
|
|
39
|
+
? [...rootSessions].sort((a, b) => Number((b.context && b.context.percent) || 0) - Number((a.context && a.context.percent) || 0))
|
|
40
|
+
: stableSessionSort(rootSessions);
|
|
30
41
|
if (!model.nodes.length) {
|
|
31
|
-
|
|
42
|
+
liveSessionGrid.innerHTML = "";
|
|
32
43
|
$("#graphBreadcrumbs").innerHTML = "";
|
|
33
44
|
$("#graphResetBtn").classList.add("hidden");
|
|
45
|
+
$("#agentMapToolbar")?.classList.add("hidden");
|
|
46
|
+
$("#controlRoomProjectToolbar")?.classList.remove("hidden");
|
|
47
|
+
$("#controlRoomListToolbar")?.classList.remove("hidden");
|
|
48
|
+
if ($("#controlRoomPageSummary")) $("#controlRoomPageSummary").textContent = t("control.page_summary", { start: 0, end: 0, total: 0 });
|
|
49
|
+
if ($("#controlRoomPagePrev")) $("#controlRoomPagePrev").disabled = true;
|
|
50
|
+
if ($("#controlRoomPageNext")) $("#controlRoomPageNext").disabled = true;
|
|
34
51
|
return 0;
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
if (focus) {
|
|
38
|
-
$("#
|
|
55
|
+
$("#agentMapToolbar")?.classList.remove("hidden");
|
|
56
|
+
$("#controlRoomProjectToolbar")?.classList.add("hidden");
|
|
57
|
+
$("#controlRoomListToolbar")?.classList.add("hidden");
|
|
58
|
+
liveSessionGrid.innerHTML = focusedGraph(focus, model, motionKind);
|
|
39
59
|
const path = graphPath(focus, model.byId);
|
|
40
60
|
$("#graphBreadcrumbs").innerHTML = `<button type="button" data-graph-reset>${esc(t("graph.task_list"))}</button>${path
|
|
41
61
|
.map((item) => {
|
|
@@ -51,17 +71,29 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
51
71
|
scheduleAgentWorkflowConnections();
|
|
52
72
|
} else {
|
|
53
73
|
const runtime = runtimeAgentSummary(model, liveTmuxEntries(state.snapshot && state.snapshot.tmux));
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
const pageSize = Math.max(1, Number(state.controlRoomPageSize || 4));
|
|
75
|
+
const maxPage = Math.max(0, Math.ceil(roots.length / pageSize) - 1);
|
|
76
|
+
state.controlRoomPage = Math.min(maxPage, Math.max(0, Number(state.controlRoomPage || 0)));
|
|
77
|
+
const startIndex = state.controlRoomPage * pageSize;
|
|
78
|
+
const endIndex = Math.min(roots.length, startIndex + pageSize);
|
|
79
|
+
const visibleRoots = roots.slice(startIndex, endIndex);
|
|
80
|
+
liveSessionGrid.innerHTML = runtimeSeparatedOverview(visibleRoots, model, roots);
|
|
81
|
+
restoreDisclosureStates(liveSessionGrid);
|
|
82
|
+
$("#graphBreadcrumbs").innerHTML = "";
|
|
83
|
+
$("#agentMapToolbar")?.classList.add("hidden");
|
|
84
|
+
$("#controlRoomProjectToolbar")?.classList.remove("hidden");
|
|
85
|
+
$("#controlRoomListToolbar")?.classList.remove("hidden");
|
|
86
|
+
if ($("#controlRoomPageSummary")) $("#controlRoomPageSummary").textContent = t("control.page_summary", {
|
|
87
|
+
start: roots.length ? startIndex + 1 : 0,
|
|
88
|
+
end: endIndex,
|
|
89
|
+
total: roots.length,
|
|
90
|
+
});
|
|
91
|
+
if ($("#controlRoomPagePrev")) $("#controlRoomPagePrev").disabled = state.controlRoomPage === 0;
|
|
92
|
+
if ($("#controlRoomPageNext")) $("#controlRoomPageNext").disabled = state.controlRoomPage >= maxPage;
|
|
61
93
|
$("#graphResetBtn").classList.add("hidden");
|
|
62
94
|
return runtime.activeCount;
|
|
63
95
|
}
|
|
64
|
-
return model.nodes.filter(
|
|
96
|
+
return model.nodes.filter(isControlRoomSession).length;
|
|
65
97
|
}
|
|
66
98
|
|
|
67
99
|
return { renderAgentMap };
|