loadtoagent 1.3.5 → 1.3.7
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 +1 -1
- package/renderer/app-dashboard.js +90 -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 +831 -31
- package/renderer/styles-readability.css +30 -15
- package/renderer/styles-responsive-shell.css +37 -9
- 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
|
@@ -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 };
|
|
@@ -20,6 +20,9 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
20
20
|
statusClass,
|
|
21
21
|
currentActivity,
|
|
22
22
|
isLiveSession,
|
|
23
|
+
isControlRoomSession = isLiveSession,
|
|
24
|
+
controlRoomStatus = session => session?.status,
|
|
25
|
+
sessionRetentionMinutes = () => 0,
|
|
23
26
|
subagentWorkState,
|
|
24
27
|
subagentWorkLabel,
|
|
25
28
|
latestWorkCopy,
|
|
@@ -30,6 +33,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
30
33
|
executionModeBadge,
|
|
31
34
|
graphDescendantCount,
|
|
32
35
|
sessionWorkspaceLabel,
|
|
36
|
+
controlRoomProject = session => ({ key: String(session?.workspace || session?.id || "unknown"), label: sessionWorkspaceLabel(session) }),
|
|
33
37
|
} = context;
|
|
34
38
|
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
35
39
|
const statusLabel = (status) => ({
|
|
@@ -44,6 +48,8 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
44
48
|
const usage = session.usage || {};
|
|
45
49
|
const percent = Math.max(0, Math.min(100, Number(context.percent || 0)));
|
|
46
50
|
const running = isLiveSession(session);
|
|
51
|
+
const presentationStatus = controlRoomStatus(session);
|
|
52
|
+
const retained = isControlRoomSession(session) && !running;
|
|
47
53
|
const childCount = (session.childIds || []).length;
|
|
48
54
|
const childMetrics = session.collaboration && session.collaboration.metrics;
|
|
49
55
|
const cumulativeChildren = childMetrics ? childMetrics.cumulativeCreated : childCount;
|
|
@@ -72,10 +78,12 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
72
78
|
<span class="provider-mark">${esc(provider.mark)}</span>
|
|
73
79
|
<span class="agent-identity"><b>${esc(role)}</b><small>${esc(provider.label)} · ${esc(session.model || t("graph.model_unknown"))}</small></span>
|
|
74
80
|
${executionModeBadge(session, true)}
|
|
75
|
-
<span class="status-pill ${statusClass(
|
|
81
|
+
<span class="status-pill ${statusClass(presentationStatus)}">${esc(statusLabel(presentationStatus))}</span>
|
|
76
82
|
</span>
|
|
77
83
|
<span class="agent-task-label">
|
|
78
|
-
${session.parentId ? t("graph.assigned_task", { source: delegation.assignmentSource === "
|
|
84
|
+
${session.parentId ? t("graph.assigned_task", { source: delegation.assignmentSource === "protected"
|
|
85
|
+
? t("graph.assignment_protected_suffix")
|
|
86
|
+
: (delegation.assignmentSource === "parent-narration" ? t("graph.main_ai_explanation_suffix") : "") }) : t("graph.current_goal")}
|
|
79
87
|
</span>
|
|
80
88
|
<strong class="agent-task" title="${esc(goalPreview.full)}">${esc(goalPreview.text)}</strong>
|
|
81
89
|
${goalPreview.truncated ? `<span class="agent-goal-note">${esc(t("graph.summary_shown"))}</span>` : ""}
|
|
@@ -103,6 +111,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
103
111
|
<span>${session.parentId
|
|
104
112
|
? (cumulativeChildren ? t("graph.subagents_created", { count: cumulativeChildren }) : t("graph.helper_ai"))
|
|
105
113
|
: t("project.origin_named", { name: sessionWorkspaceLabel(session) })}</span>
|
|
114
|
+
${retained ? `<button type="button" data-session-archive="${esc(session.id)}">${esc(t("control.move_to_history"))}</button>` : ""}
|
|
106
115
|
<button type="button" data-open-session="${esc(session.id)}">${esc(t("graph.view_conversation"))} <b>↗</b>
|
|
107
116
|
</button>
|
|
108
117
|
</footer>
|
|
@@ -113,6 +122,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
113
122
|
const provider = providerInfo(session.provider);
|
|
114
123
|
const usage = session.usage || {};
|
|
115
124
|
const directChildren = graphChildren(session, model).length;
|
|
125
|
+
const presentationStatus = controlRoomStatus(session);
|
|
116
126
|
const identity = session.parentId
|
|
117
127
|
? t("graph.helper_ai_named", { name: session.agentName || agentRoleLabel(session.agentRole) })
|
|
118
128
|
: t("project.origin_named", { name: sessionWorkspaceLabel(session) });
|
|
@@ -124,8 +134,9 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
124
134
|
const outcomeText = outcome || latestWorkCopy(session);
|
|
125
135
|
const assignedWorkPreview = readablePreview(assignedWork, session.parentId ? 110 : 104);
|
|
126
136
|
const taskLabel = session.parentId ? `${label || agentRoleLabel(session.agentRole)}${taskName ? t("graph.assigned_name_suffix", { name: taskName }) : ""}` : label;
|
|
127
|
-
const assignmentSourceNote =
|
|
128
|
-
|
|
137
|
+
const assignmentSourceNote = session.parentId && delegation.assignmentSource === "protected"
|
|
138
|
+
? `<span class="agent-flow-assignment-source">${esc(t("graph.assignment_source_protected"))}</span>`
|
|
139
|
+
: session.parentId && delegation.assignmentSource === "parent-narration"
|
|
129
140
|
? `<span class="agent-flow-assignment-source">${esc(t("graph.main_ai_prestart_explanation"))}</span>`
|
|
130
141
|
: "";
|
|
131
142
|
const sharedGoalCopy =
|
|
@@ -173,7 +184,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
173
184
|
</span>
|
|
174
185
|
</button>`;
|
|
175
186
|
}
|
|
176
|
-
return `<button type="button" class="agent-flow-row ${isLiveSession(session) ? "running" : ""} ${statusClass(
|
|
187
|
+
return `<button type="button" class="agent-flow-row ${isLiveSession(session) ? "running" : ""} ${statusClass(presentationStatus)}"
|
|
177
188
|
data-graph-focus="${esc(session.id)}"
|
|
178
189
|
data-motion-key="agent:${esc(session.id)}"
|
|
179
190
|
data-motion-value="${esc(session.updatedAt || "")}:${usage.total || 0}:${esc(session.status || "")}"
|
|
@@ -185,7 +196,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
185
196
|
<em>${esc(identity)} · ${directChildren ? `${t("graph.helper_ai_count", { count: directChildren })} · ` : ""}${esc(timeAgo(session.updatedAt))}</em>
|
|
186
197
|
${assignmentSourceNote}${sharedGoalCopy}${outcomeCopy}
|
|
187
198
|
</span>
|
|
188
|
-
<span class="agent-flow-provider"><i>${esc(provider.mark)}</i><small>${esc(statusLabel(
|
|
199
|
+
<span class="agent-flow-provider"><i>${esc(provider.mark)}</i><small>${esc(statusLabel(presentationStatus))}</small></span>
|
|
189
200
|
</button>`;
|
|
190
201
|
}
|
|
191
202
|
|
|
@@ -418,7 +429,8 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
418
429
|
const executionLabel = t("control.runtime_work", { runtime, kind: executionKind });
|
|
419
430
|
const ownerGoal = controlRoomAgentGoal(owner, 52);
|
|
420
431
|
const running = activity.status === "running";
|
|
421
|
-
|
|
432
|
+
const stateClass = running ? "is-running" : (activity.status === "unverified" ? "is-unverified" : "is-complete");
|
|
433
|
+
return `<button type="button" class="control-room-node execution-node ${stateClass}"
|
|
422
434
|
data-open-execution-owner="${esc(owner.id)}"
|
|
423
435
|
data-open-execution-id="${esc(activity.id)}"
|
|
424
436
|
data-control-summary="${esc(summary.text)}"
|
|
@@ -433,10 +445,13 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
433
445
|
|
|
434
446
|
function controlRoomSession(root, model) {
|
|
435
447
|
const provider = providerInfo(root.provider);
|
|
448
|
+
const presentationStatus = controlRoomStatus(root);
|
|
449
|
+
const waiting = presentationStatus === "waiting";
|
|
450
|
+
const retained = isControlRoomSession(root) && !isLiveSession(root);
|
|
436
451
|
const descendants = controlRoomDescendants(root, model);
|
|
437
452
|
const actors = [root, ...descendants];
|
|
438
453
|
const executionItems = actors.flatMap(owner => (owner.executions || []).map(activity => ({ activity, owner })));
|
|
439
|
-
const activeChildren = descendants.filter(child =>
|
|
454
|
+
const activeChildren = descendants.filter(child => ["starting", "running", "paused", "waiting"].includes(child.status) && !child.completionObserved);
|
|
440
455
|
const completedChildren = descendants.filter(child => ["completed", "cancelled", "failed"].includes(child.status) || child.completionObserved);
|
|
441
456
|
const activeExecutions = executionItems.filter(item => item.activity.status === "running");
|
|
442
457
|
const completedExecutions = executionItems
|
|
@@ -454,7 +469,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
454
469
|
data-control-summary="${esc(title.text)}"
|
|
455
470
|
data-motion-key="control-main:${esc(root.id)}" data-motion-value="${esc(root.updatedAt || "")}:${esc(root.status || "")}"
|
|
456
471
|
style="${providerStyle(root.provider)}">
|
|
457
|
-
<span class="control-main-top"><span class="provider-mark">${esc(provider.mark)}</span><span><small>${esc(t("control.main_agent"))}</small><b>${esc(provider.label)} · ${esc(root.model || t("session.model_unknown"))}</b></span><em><i aria-hidden="true"></i>${esc(statusLabel(
|
|
472
|
+
<span class="control-main-top"><span class="provider-mark">${esc(provider.mark)}</span><span><small>${esc(t("control.main_agent"))}</small><b>${esc(provider.label)} · ${esc(root.model || t("session.model_unknown"))}</b></span><em><i aria-hidden="true"></i>${esc(statusLabel(presentationStatus))}</em></span>
|
|
458
473
|
<strong title="${esc(title.full)}">${esc(title.text)}</strong>
|
|
459
474
|
<span class="control-main-now"><small>${esc(t("graph.current_work"))}</small><b title="${esc(current.full)}">${esc(current.text)}</b></span>
|
|
460
475
|
<span class="control-main-meta"><small>${esc(t("control.unit_counts", { helpers: activeChildren.length, executions: activeExecutions.length }))}</small><b>${esc(t("graph.view_conversation"))} →</b></span>
|
|
@@ -467,8 +482,16 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
467
482
|
const completed = completedUnits.length
|
|
468
483
|
? completedUnits.map(unit => unit.kind === "child" ? controlRoomChildNode(unit.child) : controlRoomExecutionNode(unit.item)).join("")
|
|
469
484
|
: `<div class="control-room-complete-empty"><span>✓</span><small>${esc(t("control.completed_empty"))}</small></div>`;
|
|
470
|
-
|
|
471
|
-
|
|
485
|
+
const waitingWithBackground = waiting && activeExecutions.some(item => item.activity.mode === "background" || item.activity.kind === "background");
|
|
486
|
+
const sessionStateKey = waitingWithBackground
|
|
487
|
+
? "control.waiting_background_session"
|
|
488
|
+
: (waiting ? "control.waiting_session" : "control.live_session");
|
|
489
|
+
const retention = retained ? `<small class="control-session-retention">${esc(t("control.auto_history_in_minutes", { minutes: sessionRetentionMinutes(root) }))}</small>` : "";
|
|
490
|
+
const archive = retained ? `<button type="button" class="control-session-archive" data-session-archive="${esc(root.id)}">${esc(t("control.move_to_history"))}</button>` : "";
|
|
491
|
+
return `<article class="control-room-session ${waiting ? "is-waiting" : ""} ${waitingWithBackground ? "has-background-work" : ""}" data-control-session="${esc(root.id)}" data-session-sortable="${esc(root.id)}"
|
|
492
|
+
style="${providerStyle(root.provider)}" role="group" tabindex="0" draggable="true" aria-grabbed="false"
|
|
493
|
+
aria-keyshortcuts="Alt+ArrowUp Alt+ArrowDown" aria-label="${esc(t("session.drag_label", { title: title.text }))}" aria-describedby="sessionReorderHelp">
|
|
494
|
+
<header><div><span class="control-session-live"><i></i>${esc(t(sessionStateKey))}</span><b>${esc(title.text)}</b>${retention}</div><span class="session-drag-handle" aria-hidden="true" title="${esc(t("session.reorder_hint"))}"></span>${archive}<button type="button" class="control-session-flow" data-graph-focus="${esc(root.id)}">${esc(t("control.open_full_flow"))} ↗</button></header>
|
|
472
495
|
<div class="control-room-flow">
|
|
473
496
|
<section class="control-room-column main-column"><span class="control-column-label">${esc(t("control.main_work_column"))}</span>${main}</section>
|
|
474
497
|
<span class="control-flow-link live" aria-hidden="true"><i></i></span>
|
|
@@ -479,9 +502,44 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
479
502
|
</article>`;
|
|
480
503
|
}
|
|
481
504
|
|
|
482
|
-
function runtimeSeparatedOverview(roots, model) {
|
|
505
|
+
function runtimeSeparatedOverview(roots, model, allRoots = roots) {
|
|
506
|
+
const projectDescriptor = (root) => {
|
|
507
|
+
const project = controlRoomProject(root);
|
|
508
|
+
return { key: project.key, name: project.label };
|
|
509
|
+
};
|
|
510
|
+
const allGroups = new Map();
|
|
511
|
+
allRoots.forEach((root) => {
|
|
512
|
+
const { key, name } = projectDescriptor(root);
|
|
513
|
+
if (!allGroups.has(key)) allGroups.set(key, { name, roots: [] });
|
|
514
|
+
allGroups.get(key).roots.push(root);
|
|
515
|
+
});
|
|
516
|
+
const groups = new Map();
|
|
517
|
+
roots.forEach((root) => {
|
|
518
|
+
const { key, name } = projectDescriptor(root);
|
|
519
|
+
if (!groups.has(key)) groups.set(key, { name, roots: [] });
|
|
520
|
+
groups.get(key).roots.push(root);
|
|
521
|
+
});
|
|
522
|
+
const projectGroups = [...groups.entries()].map(([key, { name, roots: projectRoots }], index) => {
|
|
523
|
+
const projectTotals = allGroups.get(key)?.roots || projectRoots;
|
|
524
|
+
const activeCount = projectTotals.filter((root) => isLiveSession(root)).length;
|
|
525
|
+
const attentionCount = projectTotals.filter((root) => !isLiveSession(root) && isControlRoomSession(root)).length;
|
|
526
|
+
const summary = attentionCount
|
|
527
|
+
? t("control.project_live_attention_summary", { active: activeCount, attention: attentionCount })
|
|
528
|
+
: t("control.project_live_summary", { active: activeCount });
|
|
529
|
+
const disclosureKey = `control-project:${key}`;
|
|
530
|
+
const presentation = index === 0 ? "is-primary" : "is-secondary";
|
|
531
|
+
const projectFocusId = projectRoots[0]?.id || "";
|
|
532
|
+
return `<details class="control-room-project-group ${presentation}" data-control-project="${esc(name)}" data-disclosure-key="${esc(disclosureKey)}" ${index === 0 ? "open" : ""}>
|
|
533
|
+
<summary class="control-project-header" data-project-toggle="${esc(name)}">
|
|
534
|
+
<span class="control-project-heading"><i aria-hidden="true">□</i><b>${esc(name)}</b><small>${esc(summary)}</small><em>${projectTotals.length}</em></span>
|
|
535
|
+
<span class="control-project-handle" role="img" aria-label="프로젝트 그룹" title="프로젝트 그룹 · 세션은 최근 활동순으로 표시됩니다"></span>
|
|
536
|
+
</summary>
|
|
537
|
+
<button type="button" class="control-project-flow-link" data-graph-focus="${esc(projectFocusId)}"><span>${esc(t("control.open_full_flow"))} ↗</span></button>
|
|
538
|
+
<div class="control-project-body">${projectRoots.map(root => controlRoomSession(root, model)).join("")}</div>
|
|
539
|
+
</details>`;
|
|
540
|
+
}).join("");
|
|
483
541
|
return `<div class="control-room-overview" data-control-room-overview="true">
|
|
484
|
-
${
|
|
542
|
+
${projectGroups}
|
|
485
543
|
</div>`;
|
|
486
544
|
}
|
|
487
545
|
|
|
@@ -609,7 +667,9 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
609
667
|
: event.kind === "started"
|
|
610
668
|
? t("graph.runtime_start_confirmed")
|
|
611
669
|
: t("graph.status_only_recorded"));
|
|
612
|
-
const sourceLabel = event.assignmentSource === "
|
|
670
|
+
const sourceLabel = event.assignmentSource === "protected"
|
|
671
|
+
? ` · ${t("graph.assignment_source_protected_short")}`
|
|
672
|
+
: (event.assignmentSource === "parent-narration" ? ` · ${t("graph.main_ai_prestart_short")}` : "");
|
|
613
673
|
return `<article class="agent-communication-event ${esc(event.kind)}" data-communication-kind="${esc(event.kind)}">
|
|
614
674
|
<span class="communication-route">
|
|
615
675
|
<b>${esc(communicationEndpoint(event.from, owner, model))}</b><i>→</i>
|
|
@@ -650,6 +710,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
650
710
|
running: t("graph.execution_running"),
|
|
651
711
|
completed: t("graph.execution_completed"),
|
|
652
712
|
failed: t("graph.execution_failed"),
|
|
713
|
+
unverified: t("graph.execution_unverified"),
|
|
653
714
|
cancelled: t("ui.stopped"),
|
|
654
715
|
})[activity.status] || activity.status;
|
|
655
716
|
}
|
package/renderer/app-quality.js
CHANGED
|
@@ -94,6 +94,7 @@ window.LoadToAgentAppFactories.createQualityEnhancements = function createQualit
|
|
|
94
94
|
? dashboard.workspace
|
|
95
95
|
: "all";
|
|
96
96
|
state.sort = ["recent", "tokens", "context"].includes(dashboard.sort) ? dashboard.sort : "recent";
|
|
97
|
+
state.controlRoomSort = ["recent", "tokens", "context"].includes(dashboard.controlRoomSort) ? dashboard.controlRoomSort : "recent";
|
|
97
98
|
state.sessionOrder = Array.isArray(dashboard.sessionOrder)
|
|
98
99
|
? dashboard.sessionOrder.filter(id => typeof id === "string" && id.length <= 500).slice(0, 1_000)
|
|
99
100
|
: [];
|
|
@@ -130,6 +131,7 @@ window.LoadToAgentAppFactories.createQualityEnhancements = function createQualit
|
|
|
130
131
|
providers: [...state.providerFilters],
|
|
131
132
|
workspace: String(state.workspace || "all").slice(0, 2_000),
|
|
132
133
|
sort: ["recent", "tokens", "context"].includes(state.sort) ? state.sort : "recent",
|
|
134
|
+
controlRoomSort: ["recent", "tokens", "context"].includes(state.controlRoomSort) ? state.controlRoomSort : "recent",
|
|
133
135
|
sessionOrder: (state.sessionOrder || []).filter(id => typeof id === "string" && id.length <= 500).slice(0, 1_000),
|
|
134
136
|
};
|
|
135
137
|
try {
|
|
@@ -24,6 +24,7 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
24
24
|
statusClass,
|
|
25
25
|
currentActivity,
|
|
26
26
|
isLiveSession,
|
|
27
|
+
isControlRoomSession = isLiveSession,
|
|
27
28
|
latestWorkCopy,
|
|
28
29
|
statusIcon,
|
|
29
30
|
renderProviderRail,
|
|
@@ -75,11 +76,13 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
75
76
|
const originLabel = sessionWorkspaceLabel(session);
|
|
76
77
|
return `<article class="session-card session-record ${opts.live ? "live-card" : ""} ${statusClass(session.status)} ${session.parentId ? "subagent" : ""}"
|
|
77
78
|
data-session-id="${esc(session.id)}"
|
|
79
|
+
data-session-sortable="${esc(session.id)}"
|
|
78
80
|
data-motion-key="session:${esc(session.id)}"
|
|
79
81
|
data-motion-value="${esc(session.updatedAt || "")}:${esc(session.status || "")}"
|
|
80
82
|
style="${providerStyle(session.provider)}"
|
|
81
|
-
role="button" tabindex="0"
|
|
82
|
-
aria-
|
|
83
|
+
role="button" tabindex="0" draggable="true" aria-grabbed="false"
|
|
84
|
+
aria-keyshortcuts="Alt+ArrowUp Alt+ArrowDown"
|
|
85
|
+
aria-labelledby="${accessibleId}-title" aria-describedby="${accessibleId}-summary sessionReorderHelp">
|
|
83
86
|
<div class="card-head">
|
|
84
87
|
<span class="provider-mark">${esc(provider.mark)}</span>
|
|
85
88
|
<div class="card-head-main"><div class="card-provider-line"><b>${esc(provider.label)}</b><span>${esc(session.model || t("session.model_unknown"))}</span></div></div>
|
|
@@ -95,10 +98,7 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
95
98
|
</div>
|
|
96
99
|
<footer class="card-footer">
|
|
97
100
|
<span>${esc(timeAgo(session.updatedAt))}</span>
|
|
98
|
-
<span class="session-
|
|
99
|
-
<button type="button" data-session-order-move="${esc(session.id)}" data-session-order-offset="-1" title="${esc(t("session.move_up"))}" aria-label="${esc(t("session.move_up"))}">↑</button>
|
|
100
|
-
<button type="button" data-session-order-move="${esc(session.id)}" data-session-order-offset="1" title="${esc(t("session.move_down"))}" aria-label="${esc(t("session.move_down"))}">↓</button>
|
|
101
|
-
</span>
|
|
101
|
+
<span class="session-drag-handle" aria-hidden="true" title="${esc(t("session.reorder_hint"))}"></span>
|
|
102
102
|
<strong>${esc(t("graph.view_conversation"))}<i aria-hidden="true">→</i></strong>
|
|
103
103
|
</footer>
|
|
104
104
|
</article>`;
|
|
@@ -166,7 +166,7 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
166
166
|
const attentionCount = attentionView ? renderAttentionInbox() : 0;
|
|
167
167
|
const showMap = ["all", "active"].includes(state.view);
|
|
168
168
|
const graphLiveCount = showMap ? renderAgentMap(graphFilteredSessions(), motionKind) : 0;
|
|
169
|
-
const regular = state.view === "all" ? sessions.filter((session) => !
|
|
169
|
+
const regular = state.view === "all" ? sessions.filter((session) => !isControlRoomSession(session)) : state.view === "active" ? [] : sessions;
|
|
170
170
|
const visible = regular.slice(0, state.visibleLimit);
|
|
171
171
|
const resultCount = attentionView ? attentionCount : graphLiveCount + regular.length;
|
|
172
172
|
$("#sessionResultSummary").textContent = window.LoadToAgentI18n.t("quality.results_summary", { count: resultCount });
|