loadtoagent 1.3.10 → 1.3.12
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 +6 -3
- package/README.md +6 -3
- package/README.zh-CN.md +6 -3
- package/bin/loadtoagent.js +3 -1
- package/docs/ARCHITECTURE.md +7 -0
- package/docs/MANAGED-TERMINAL-SESSIONS.md +64 -0
- package/main.js +5 -1
- package/package.json +2 -1
- package/preload.js +3 -0
- package/renderer/app-agent-actions.js +83 -35
- package/renderer/app-dashboard.js +41 -3
- package/renderer/app-drawer-content.js +4 -1
- package/renderer/app-drawer.js +107 -26
- package/renderer/app-events-dialogs.js +66 -8
- package/renderer/app-events-filters.js +3 -1
- package/renderer/app-events-sessions.js +3 -23
- package/renderer/app-graph-orchestration.js +6 -4
- package/renderer/app-graph-view.js +74 -16
- package/renderer/app-session-render.js +5 -1
- package/renderer/app.js +11 -1
- package/renderer/i18n-messages.js +35 -0
- package/renderer/index.html +2 -0
- package/renderer/styles-components.css +48 -0
- package/renderer/styles-control-room.css +444 -0
- package/renderer/styles-terminal.css +6 -0
- package/renderer/terminal-events.js +30 -4
- package/renderer/terminal-workbench.js +15 -3
- package/renderer/terminal.js +2 -0
- package/src/agentMonitor/codexParser.js +6 -2
- package/src/agentMonitor/executionActivity.js +3 -4
- package/src/bridgeServer.js +1 -1
- package/src/ipc/registerTerminalIpc.js +1 -1
- package/src/managedTmuxRuntime.js +53 -0
- package/src/terminalHost.js +8 -2
- package/src/terminalManager.js +173 -33
package/renderer/app-drawer.js
CHANGED
|
@@ -22,7 +22,59 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
22
22
|
failed: "control.delivery_failed",
|
|
23
23
|
})[phase] || "control.delivery_confirming";
|
|
24
24
|
|
|
25
|
-
function
|
|
25
|
+
function scheduleFlowConnections() {
|
|
26
|
+
requestAnimationFrame(() => {
|
|
27
|
+
window.LoadToAgentApp?.scheduleAgentWorkflowConnections?.();
|
|
28
|
+
window.LoadToAgentApp?.drawAgentWorkflowConnections?.();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolvedPresentation(options = {}) {
|
|
33
|
+
const requested = options.presentation || (options.context ? "context" : "modal");
|
|
34
|
+
if (requested !== "context") return "modal";
|
|
35
|
+
return state.view === "all" && window.matchMedia("(min-width: 1280px)").matches
|
|
36
|
+
? "context"
|
|
37
|
+
: "modal";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setDrawerPresentation(presentation) {
|
|
41
|
+
const drawer = $("#detailDrawer");
|
|
42
|
+
const contextual = presentation === "context";
|
|
43
|
+
state.drawerPresentation = presentation;
|
|
44
|
+
drawer.dataset.presentation = presentation;
|
|
45
|
+
drawer.setAttribute("role", contextual ? "complementary" : "dialog");
|
|
46
|
+
if (contextual) drawer.removeAttribute("aria-modal");
|
|
47
|
+
else drawer.setAttribute("aria-modal", "true");
|
|
48
|
+
$("#drawerBackToFlowBtn").classList.toggle("hidden", !contextual);
|
|
49
|
+
$("#drawerResizeHandle").classList.toggle("hidden", !contextual);
|
|
50
|
+
document.body.classList.toggle("conversation-context-open", contextual);
|
|
51
|
+
const currentWidth = Math.round(drawer.getBoundingClientRect().width || 640);
|
|
52
|
+
$("#drawerResizeHandle").setAttribute("aria-valuenow", String(currentWidth));
|
|
53
|
+
if (contextual) {
|
|
54
|
+
$("#drawerBackdrop").classList.add("hidden");
|
|
55
|
+
$("#drawerBackdrop").classList.remove("closing");
|
|
56
|
+
} else {
|
|
57
|
+
$("#drawerBackdrop").classList.remove("hidden");
|
|
58
|
+
$("#drawerBackdrop").classList.remove("closing");
|
|
59
|
+
}
|
|
60
|
+
scheduleFlowConnections();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function openDrawerSurface(presentation) {
|
|
64
|
+
clearTimeout(motionState.drawerTimer);
|
|
65
|
+
setDrawerPresentation(presentation);
|
|
66
|
+
$("#detailDrawer").classList.add("open");
|
|
67
|
+
if (presentation === "modal") {
|
|
68
|
+
setDialogOpenState($("#detailDrawer"), true);
|
|
69
|
+
} else {
|
|
70
|
+
$("#detailDrawer").removeAttribute("inert");
|
|
71
|
+
$("#detailDrawer").setAttribute("aria-hidden", "false");
|
|
72
|
+
$("#appShell")?.removeAttribute("inert");
|
|
73
|
+
document.body.classList.remove("dialog-open");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function openDrawer(id, options = {}) {
|
|
26
78
|
rememberDialogTrigger();
|
|
27
79
|
markGuideStep("detail");
|
|
28
80
|
state.selectedId = id;
|
|
@@ -30,19 +82,18 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
30
82
|
state.drawerExecutionId = null;
|
|
31
83
|
state.drawerTab = "chat";
|
|
32
84
|
state.drawerForceLatest = true;
|
|
33
|
-
|
|
34
|
-
$("#drawerBackdrop").classList.remove("hidden");
|
|
35
|
-
$("#drawerBackdrop").classList.remove("closing");
|
|
36
|
-
$("#detailDrawer").classList.add("open");
|
|
37
|
-
setDialogOpenState($("#detailDrawer"), true);
|
|
85
|
+
openDrawerSurface(resolvedPresentation(options));
|
|
38
86
|
renderDrawer();
|
|
39
87
|
loadSessionDetail(id, true);
|
|
40
|
-
setTimeout(
|
|
88
|
+
setTimeout(
|
|
89
|
+
() => (state.drawerPresentation === "modal" ? $("#closeDrawerBtn") : $("#drawerBackToFlowBtn")).focus({ preventScroll: true }),
|
|
90
|
+
0,
|
|
91
|
+
);
|
|
41
92
|
}
|
|
42
93
|
|
|
43
|
-
function openSubagentConversation(id) {
|
|
94
|
+
function openSubagentConversation(id, options = {}) {
|
|
44
95
|
const child = snapshotSession(id) || state.details.get(id);
|
|
45
|
-
if (!child || !child.parentId) return openDrawer(id);
|
|
96
|
+
if (!child || !child.parentId) return openDrawer(id, options);
|
|
46
97
|
rememberDialogTrigger();
|
|
47
98
|
markGuideStep("detail");
|
|
48
99
|
state.selectedId = id;
|
|
@@ -51,15 +102,14 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
51
102
|
state.drawerTab = "chat";
|
|
52
103
|
state.agentCommandRoutes.delete(id);
|
|
53
104
|
state.drawerForceLatest = true;
|
|
54
|
-
|
|
55
|
-
$("#drawerBackdrop").classList.remove("hidden");
|
|
56
|
-
$("#drawerBackdrop").classList.remove("closing");
|
|
57
|
-
$("#detailDrawer").classList.add("open");
|
|
58
|
-
setDialogOpenState($("#detailDrawer"), true);
|
|
105
|
+
openDrawerSurface(resolvedPresentation(options));
|
|
59
106
|
renderDrawer();
|
|
60
107
|
loadSessionDetail(id);
|
|
61
108
|
loadSubagentParentDetail(child);
|
|
62
|
-
setTimeout(
|
|
109
|
+
setTimeout(
|
|
110
|
+
() => (state.drawerPresentation === "modal" ? $("#closeDrawerBtn") : $("#drawerBackToFlowBtn")).focus({ preventScroll: true }),
|
|
111
|
+
0,
|
|
112
|
+
);
|
|
63
113
|
}
|
|
64
114
|
|
|
65
115
|
function openExecutionActivity(ownerId, executionId) {
|
|
@@ -72,11 +122,7 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
72
122
|
state.drawerExecutionId = executionId;
|
|
73
123
|
state.drawerTab = "chat";
|
|
74
124
|
state.drawerForceLatest = false;
|
|
75
|
-
|
|
76
|
-
$("#drawerBackdrop").classList.remove("hidden");
|
|
77
|
-
$("#drawerBackdrop").classList.remove("closing");
|
|
78
|
-
$("#detailDrawer").classList.add("open");
|
|
79
|
-
setDialogOpenState($("#detailDrawer"), true);
|
|
125
|
+
openDrawerSurface("modal");
|
|
80
126
|
renderDrawer();
|
|
81
127
|
loadSessionDetail(ownerId);
|
|
82
128
|
if (owner.parentId) loadSubagentParentDetail(owner);
|
|
@@ -85,15 +131,27 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
85
131
|
|
|
86
132
|
function closeDrawer(restoreFocus = true) {
|
|
87
133
|
if (!$("#detailDrawer").classList.contains("open")) return;
|
|
134
|
+
const presentation = state.drawerPresentation;
|
|
88
135
|
const drawerGeneration = motionState.dialogGeneration;
|
|
89
136
|
$("#detailDrawer").classList.remove("open");
|
|
90
|
-
|
|
91
|
-
|
|
137
|
+
if (presentation === "modal") {
|
|
138
|
+
setDialogOpenState($("#detailDrawer"), false);
|
|
139
|
+
$("#drawerBackdrop").classList.add("closing");
|
|
140
|
+
} else {
|
|
141
|
+
$("#detailDrawer").setAttribute("inert", "");
|
|
142
|
+
$("#detailDrawer").setAttribute("aria-hidden", "true");
|
|
143
|
+
$("#drawerBackdrop").classList.add("hidden");
|
|
144
|
+
$("#drawerBackdrop").classList.remove("closing");
|
|
145
|
+
document.body.classList.remove("conversation-context-open");
|
|
146
|
+
scheduleFlowConnections();
|
|
147
|
+
}
|
|
92
148
|
clearTimeout(motionState.drawerTimer);
|
|
93
149
|
motionState.drawerTimer = setTimeout(
|
|
94
150
|
() => {
|
|
95
151
|
$("#drawerBackdrop").classList.add("hidden");
|
|
96
152
|
$("#drawerBackdrop").classList.remove("closing");
|
|
153
|
+
setDrawerPresentation("modal");
|
|
154
|
+
$("#drawerBackdrop").classList.add("hidden");
|
|
97
155
|
if (drawerGeneration !== motionState.dialogGeneration) return;
|
|
98
156
|
if (restoreFocus) restoreDialogTrigger(drawerGeneration);
|
|
99
157
|
else motionState.activeDialogTrigger = null;
|
|
@@ -102,6 +160,10 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
102
160
|
);
|
|
103
161
|
}
|
|
104
162
|
|
|
163
|
+
function backToAgentFlow() {
|
|
164
|
+
closeDrawer();
|
|
165
|
+
}
|
|
166
|
+
|
|
105
167
|
function renderDrawer() {
|
|
106
168
|
const session = selectedSession();
|
|
107
169
|
if (!session) return closeDrawer();
|
|
@@ -124,7 +186,9 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
124
186
|
$("#detailDrawer").style.setProperty("--drawer-provider", provider.accent);
|
|
125
187
|
$("#drawerProviderMark").style.setProperty("--provider", provider.accent);
|
|
126
188
|
$("#drawerProviderMark").textContent = executionMode && activity?.kind === "shell" ? ">_" : provider.mark;
|
|
127
|
-
$("#drawerProvider").textContent = executionMode
|
|
189
|
+
$("#drawerProvider").textContent = state.drawerPresentation !== "modal" && !executionMode
|
|
190
|
+
? `${t("drawer.agent_flow")} · ${presentationLabel}`
|
|
191
|
+
: executionMode
|
|
128
192
|
? `${activity?.runtime || activity?.tool || t("drawer.execution_unit")} · ${activity ? context.executionActivityStatus(activity) : t("drawer.unknown")}`
|
|
129
193
|
: subagentMode
|
|
130
194
|
? `${t("control.subagent")} · ${presentationLabel}`
|
|
@@ -185,8 +249,14 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
185
249
|
tab.tabIndex = active ? 0 : -1;
|
|
186
250
|
});
|
|
187
251
|
const activeTab = $(`.drawer-tab[data-tab="${state.drawerTab}"]`);
|
|
188
|
-
if (activeTab) $("#drawerContent").setAttribute("aria-labelledby", activeTab.id);
|
|
189
252
|
const content = $("#drawerContent");
|
|
253
|
+
if (subagentMode && state.drawerPresentation === "context") {
|
|
254
|
+
content.removeAttribute("aria-labelledby");
|
|
255
|
+
content.setAttribute("aria-label", t("control.subagent_conversation"));
|
|
256
|
+
} else {
|
|
257
|
+
content.removeAttribute("aria-label");
|
|
258
|
+
if (activeTab) content.setAttribute("aria-labelledby", activeTab.id);
|
|
259
|
+
}
|
|
190
260
|
rememberDisclosureStates(content);
|
|
191
261
|
const previousTop = content.scrollTop;
|
|
192
262
|
const wasAtBottom = window.LoadToAgentRendererUtils.isScrolledToEnd(content);
|
|
@@ -223,7 +293,11 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
223
293
|
const composer = $("#drawerComposer");
|
|
224
294
|
const showComposer = !executionMode && !detailLoading && !detailError && state.drawerTab === "chat" && typeof agentCommandComposer === "function";
|
|
225
295
|
composer.classList.toggle("hidden", !showComposer);
|
|
226
|
-
const nextComposerHtml = showComposer ? agentCommandComposer(session, {
|
|
296
|
+
const nextComposerHtml = showComposer ? agentCommandComposer(session, {
|
|
297
|
+
conversation: true,
|
|
298
|
+
delivery,
|
|
299
|
+
deliveryLabel: delivery ? t(deliveryLabelKey(delivery.phase)) : "",
|
|
300
|
+
}) : "";
|
|
227
301
|
const focusedDraft = document.activeElement?.matches?.("[data-agent-command-draft]")
|
|
228
302
|
&& composer.contains(document.activeElement)
|
|
229
303
|
&& document.activeElement.dataset.agentCommandDraft === session.id;
|
|
@@ -272,5 +346,12 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
272
346
|
}
|
|
273
347
|
}
|
|
274
348
|
|
|
275
|
-
return {
|
|
349
|
+
return {
|
|
350
|
+
openDrawer,
|
|
351
|
+
openSubagentConversation,
|
|
352
|
+
openExecutionActivity,
|
|
353
|
+
closeDrawer,
|
|
354
|
+
backToAgentFlow,
|
|
355
|
+
renderDrawer,
|
|
356
|
+
};
|
|
276
357
|
};
|
|
@@ -6,10 +6,10 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
6
6
|
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
7
7
|
const {
|
|
8
8
|
$, $$, state, providerInfo, visibleProviders = () => state.providers, renderProviderRail, scheduleAgentWorkflowConnections, resumeAgentTerminal, loadSessionDetail,
|
|
9
|
-
closeDrawer, renderDrawer, providerPickerHtml, syncRunComposer, openRunModal, closeRunModal, toast, performUiAction,
|
|
9
|
+
closeDrawer, backToAgentFlow, renderDrawer, providerPickerHtml, syncRunComposer, openRunModal, closeRunModal, toast, performUiAction,
|
|
10
10
|
handleRun, trapDialogFocus, currentDialog, selectView, saveRunDraft = () => {}, safeBackdrop = null,
|
|
11
11
|
copyText = async () => false,
|
|
12
|
-
dispatchAgentCommand, controlManagedRun, quickRespond, prepareReassignment, openSubagentConversation,
|
|
12
|
+
dispatchAgentCommand, openAgentTerminal, controlManagedRun, quickRespond, prepareReassignment, openSubagentConversation,
|
|
13
13
|
} = context;
|
|
14
14
|
|
|
15
15
|
function bindRunComposerEvents() {
|
|
@@ -133,8 +133,49 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
133
133
|
|
|
134
134
|
function bindDrawerAndGlobalEvents() {
|
|
135
135
|
$("#closeDrawerBtn").addEventListener("click", closeDrawer);
|
|
136
|
+
$("#drawerBackToFlowBtn").addEventListener("click", backToAgentFlow);
|
|
136
137
|
if (safeBackdrop) safeBackdrop($("#drawerBackdrop"), closeDrawer, $("#detailDrawer"));
|
|
137
138
|
else $("#drawerBackdrop").addEventListener("click", closeDrawer);
|
|
139
|
+
const resizeHandle = $("#drawerResizeHandle");
|
|
140
|
+
const setConversationPanelWidth = (nextWidth) => {
|
|
141
|
+
const maximum = Math.max(560, Math.min(860, window.innerWidth - 700));
|
|
142
|
+
const width = Math.max(560, Math.min(maximum, Math.round(nextWidth)));
|
|
143
|
+
document.documentElement.style.setProperty("--conversation-panel-width", `${width}px`);
|
|
144
|
+
resizeHandle.setAttribute("aria-valuemax", String(maximum));
|
|
145
|
+
resizeHandle.setAttribute("aria-valuenow", String(width));
|
|
146
|
+
scheduleAgentWorkflowConnections();
|
|
147
|
+
};
|
|
148
|
+
resizeHandle.addEventListener("pointerdown", (event) => {
|
|
149
|
+
if (state.drawerPresentation !== "context" || event.button !== 0) return;
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
resizeHandle.setPointerCapture?.(event.pointerId);
|
|
152
|
+
document.body.classList.add("conversation-panel-resizing");
|
|
153
|
+
setConversationPanelWidth(window.innerWidth - event.clientX);
|
|
154
|
+
});
|
|
155
|
+
resizeHandle.addEventListener("pointermove", (event) => {
|
|
156
|
+
if (!document.body.classList.contains("conversation-panel-resizing")) return;
|
|
157
|
+
setConversationPanelWidth(window.innerWidth - event.clientX);
|
|
158
|
+
});
|
|
159
|
+
const finishPanelResize = (event) => {
|
|
160
|
+
if (!document.body.classList.contains("conversation-panel-resizing")) return;
|
|
161
|
+
document.body.classList.remove("conversation-panel-resizing");
|
|
162
|
+
try { resizeHandle.releasePointerCapture?.(event.pointerId); } catch {}
|
|
163
|
+
scheduleAgentWorkflowConnections();
|
|
164
|
+
};
|
|
165
|
+
resizeHandle.addEventListener("pointerup", finishPanelResize);
|
|
166
|
+
resizeHandle.addEventListener("pointercancel", finishPanelResize);
|
|
167
|
+
resizeHandle.addEventListener("keydown", (event) => {
|
|
168
|
+
if (state.drawerPresentation !== "context" || !["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
const current = Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--conversation-panel-width"))
|
|
171
|
+
|| $("#detailDrawer").getBoundingClientRect().width;
|
|
172
|
+
const next = event.key === "Home"
|
|
173
|
+
? 560
|
|
174
|
+
: event.key === "End"
|
|
175
|
+
? 860
|
|
176
|
+
: current + (event.key === "ArrowLeft" ? 32 : -32);
|
|
177
|
+
setConversationPanelWidth(next);
|
|
178
|
+
});
|
|
138
179
|
$(".drawer-tabs").addEventListener("click", (event) => {
|
|
139
180
|
const tab = event.target.closest("[data-tab]");
|
|
140
181
|
if (tab) {
|
|
@@ -163,14 +204,23 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
163
204
|
$("#detailDrawer").addEventListener("click", async (event) => {
|
|
164
205
|
const subagent = event.target.closest("[data-open-subagent-chat]");
|
|
165
206
|
if (subagent) {
|
|
166
|
-
openSubagentConversation(subagent.dataset.openSubagentChat);
|
|
207
|
+
openSubagentConversation(subagent.dataset.openSubagentChat, { presentation: state.drawerPresentation });
|
|
167
208
|
return;
|
|
168
209
|
}
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
171
|
-
|
|
210
|
+
const inputMode = event.target.closest("[data-agent-command-input-mode]");
|
|
211
|
+
if (inputMode) {
|
|
212
|
+
const sessionId = inputMode.dataset.agentCommandSession;
|
|
213
|
+
const nextMode = inputMode.dataset.agentCommandInputMode;
|
|
214
|
+
state.agentCommandInputModes.set(sessionId, nextMode);
|
|
172
215
|
renderDrawer();
|
|
173
|
-
requestAnimationFrame(() =>
|
|
216
|
+
requestAnimationFrame(() => {
|
|
217
|
+
const target = nextMode === "terminal"
|
|
218
|
+
? $("#detailDrawer")?.querySelector(`[data-agent-command-draft="${CSS.escape(sessionId)}"]`)
|
|
219
|
+
: $("#detailDrawer")?.querySelector(
|
|
220
|
+
`[data-agent-command-session="${CSS.escape(sessionId)}"][data-agent-command-input-mode="terminal"]`,
|
|
221
|
+
);
|
|
222
|
+
target?.focus({ preventScroll: true });
|
|
223
|
+
});
|
|
174
224
|
return;
|
|
175
225
|
}
|
|
176
226
|
const copy = event.target.closest("[data-copy-text]");
|
|
@@ -178,6 +228,11 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
178
228
|
await copyText(copy.dataset.copyText);
|
|
179
229
|
return;
|
|
180
230
|
}
|
|
231
|
+
const terminal = event.target.closest("[data-agent-terminal-open]");
|
|
232
|
+
if (terminal) {
|
|
233
|
+
await openAgentTerminal(terminal.dataset.agentTerminalOpen);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
181
236
|
const resume = event.target.closest("[data-resume-agent]");
|
|
182
237
|
if (resume) {
|
|
183
238
|
if (resume.dataset.busy === "true") return;
|
|
@@ -278,7 +333,10 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
278
333
|
} else if (!$("#runModal").classList.contains("hidden")) closeRunModal();
|
|
279
334
|
else closeDrawer();
|
|
280
335
|
});
|
|
281
|
-
window.addEventListener("resize",
|
|
336
|
+
window.addEventListener("resize", () => {
|
|
337
|
+
scheduleAgentWorkflowConnections();
|
|
338
|
+
if (window.innerWidth < 1280 && state.drawerPresentation === "context") closeDrawer(false);
|
|
339
|
+
});
|
|
282
340
|
}
|
|
283
341
|
|
|
284
342
|
function bindDialogAndGlobalEvents() {
|
|
@@ -61,7 +61,9 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
61
61
|
const item = event.target.closest("[data-workspace]");
|
|
62
62
|
if (item) {
|
|
63
63
|
const label = item.querySelector("strong")?.textContent.trim() || t("project.all");
|
|
64
|
-
state.workspace = item.dataset.workspace
|
|
64
|
+
state.workspace = item.dataset.workspace !== "all" && state.workspace === item.dataset.workspace
|
|
65
|
+
? "all"
|
|
66
|
+
: item.dataset.workspace;
|
|
65
67
|
state.visibleLimit = 30;
|
|
66
68
|
renderWorkspaces();
|
|
67
69
|
renderSessions("filter");
|
|
@@ -218,12 +218,6 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
218
218
|
|
|
219
219
|
function bindManagementEvents() {
|
|
220
220
|
$("#operationsOverview").addEventListener("click", async (event) => {
|
|
221
|
-
const route = event.target.closest("[data-agent-command-route]");
|
|
222
|
-
if (route) {
|
|
223
|
-
state.agentCommandRoutes.set(route.dataset.agentCommandSession, route.dataset.agentCommandRoute);
|
|
224
|
-
renderSessions("route");
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
221
|
const intervention = event.target.closest("[data-supervision-intervention-open]");
|
|
228
222
|
if (intervention) {
|
|
229
223
|
const details = $("#operationsOverview")?.querySelector(`.supervision-intervention[data-disclosure-key="supervision:command:${CSS.escape(intervention.dataset.supervisionInterventionOpen)}"]`);
|
|
@@ -290,12 +284,6 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
290
284
|
dispatchAgentCommand(form.dataset.agentCommandForm, form);
|
|
291
285
|
});
|
|
292
286
|
$("#attentionInbox").addEventListener("click", async (event) => {
|
|
293
|
-
const route = event.target.closest("[data-agent-command-route]");
|
|
294
|
-
if (route) {
|
|
295
|
-
state.agentCommandRoutes.set(route.dataset.agentCommandSession, route.dataset.agentCommandRoute);
|
|
296
|
-
renderSessions("route");
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
287
|
const filter = event.target.closest("[data-management-inbox-filter]");
|
|
300
288
|
if (filter) {
|
|
301
289
|
state.managementFilter = filter.dataset.managementInboxFilter;
|
|
@@ -446,14 +434,6 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
446
434
|
}
|
|
447
435
|
return;
|
|
448
436
|
}
|
|
449
|
-
const route = event.target.closest("[data-agent-command-route]");
|
|
450
|
-
if (route) {
|
|
451
|
-
event.stopPropagation();
|
|
452
|
-
state.agentCommandRoutes.set(route.dataset.agentCommandSession, route.dataset.agentCommandRoute);
|
|
453
|
-
renderSessions("route");
|
|
454
|
-
requestAnimationFrame(() => $("#liveSessionGrid")?.querySelector(`[data-agent-command-session="${CSS.escape(route.dataset.agentCommandSession)}"][data-agent-command-route="${CSS.escape(route.dataset.agentCommandRoute)}"]`)?.focus({ preventScroll: true }));
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
437
|
const copy = event.target.closest("[data-copy-text]");
|
|
458
438
|
if (copy) {
|
|
459
439
|
event.stopPropagation();
|
|
@@ -520,18 +500,18 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
520
500
|
const open = event.target.closest("[data-open-session]");
|
|
521
501
|
if (open) {
|
|
522
502
|
event.stopPropagation();
|
|
523
|
-
openDrawer(open.dataset.openSession);
|
|
503
|
+
openDrawer(open.dataset.openSession, { context: true });
|
|
524
504
|
return;
|
|
525
505
|
}
|
|
526
506
|
const subagentChat = event.target.closest("[data-open-subagent-chat]");
|
|
527
507
|
if (subagentChat) {
|
|
528
508
|
event.stopPropagation();
|
|
529
|
-
openSubagentConversation(subagentChat.dataset.openSubagentChat);
|
|
509
|
+
openSubagentConversation(subagentChat.dataset.openSubagentChat, { context: true });
|
|
530
510
|
return;
|
|
531
511
|
}
|
|
532
512
|
const node = event.target.closest("[data-graph-focus]");
|
|
533
513
|
if (!node) return;
|
|
534
|
-
if (state.graphFocusId === node.dataset.graphFocus) openDrawer(node.dataset.graphFocus);
|
|
514
|
+
if (state.graphFocusId === node.dataset.graphFocus) openDrawer(node.dataset.graphFocus, { context: true });
|
|
535
515
|
else {
|
|
536
516
|
state.graphFocusId = node.dataset.graphFocus;
|
|
537
517
|
renderSessions("focus");
|
|
@@ -17,6 +17,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
17
17
|
stableSessionSort = sessions => [...sessions],
|
|
18
18
|
runtimeAgentSummary,
|
|
19
19
|
liveTmuxEntries,
|
|
20
|
+
filteredLiveTmuxEntries,
|
|
20
21
|
runtimeSeparatedOverview,
|
|
21
22
|
focusedGraph,
|
|
22
23
|
scheduleAgentWorkflowConnections,
|
|
@@ -39,6 +40,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
39
40
|
&& liveSessionGrid.contains(document.activeElement);
|
|
40
41
|
rememberDisclosureStates(liveSessionGrid);
|
|
41
42
|
const model = connectedGraphSessions(sessions);
|
|
43
|
+
const tmuxEntries = filteredLiveTmuxEntries(model, liveTmuxEntries(state.snapshot && state.snapshot.tmux));
|
|
42
44
|
const focus =
|
|
43
45
|
state.graphFocusId && model.byId.get(state.graphFocusId) && model.included.has(state.graphFocusId) ? model.byId.get(state.graphFocusId) : null;
|
|
44
46
|
if (state.graphFocusId && !focus) state.graphFocusId = null;
|
|
@@ -48,7 +50,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
48
50
|
: state.controlRoomSort === "context"
|
|
49
51
|
? [...rootSessions].sort((a, b) => Number((b.context && b.context.percent) || 0) - Number((a.context && a.context.percent) || 0))
|
|
50
52
|
: stableSessionSort(rootSessions);
|
|
51
|
-
if (!model.nodes.length) {
|
|
53
|
+
if (!model.nodes.length && !tmuxEntries.length) {
|
|
52
54
|
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = "";
|
|
53
55
|
$("#graphBreadcrumbs").innerHTML = "";
|
|
54
56
|
$("#graphResetBtn").classList.add("hidden");
|
|
@@ -78,8 +80,8 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
78
80
|
$("#graphResetBtn").classList.remove("hidden");
|
|
79
81
|
scheduleAgentWorkflowConnections();
|
|
80
82
|
} else {
|
|
81
|
-
const runtime = runtimeAgentSummary(model,
|
|
82
|
-
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = runtimeSeparatedOverview(roots, model, roots);
|
|
83
|
+
const runtime = runtimeAgentSummary(model, tmuxEntries);
|
|
84
|
+
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = runtimeSeparatedOverview(roots, model, roots, tmuxEntries);
|
|
83
85
|
restoreDisclosureStates(liveSessionGrid);
|
|
84
86
|
$("#graphBreadcrumbs").innerHTML = "";
|
|
85
87
|
$("#agentMapToolbar")?.classList.add("hidden");
|
|
@@ -87,7 +89,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
87
89
|
$("#controlRoomListToolbar")?.classList.remove("hidden");
|
|
88
90
|
syncControlRoomDisclosureButtons();
|
|
89
91
|
$("#graphResetBtn").classList.add("hidden");
|
|
90
|
-
return runtime.activeCount;
|
|
92
|
+
return runtime.activeCount + tmuxEntries.length;
|
|
91
93
|
}
|
|
92
94
|
return model.nodes.filter(isControlRoomSession).length;
|
|
93
95
|
}
|
|
@@ -35,6 +35,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
35
35
|
graphDescendantCount,
|
|
36
36
|
sessionWorkspaceLabel,
|
|
37
37
|
controlRoomProject = session => ({ key: String(session?.workspace || session?.id || "unknown"), label: sessionWorkspaceLabel(session) }),
|
|
38
|
+
matchesWorkspaceFilter = () => true,
|
|
38
39
|
ensureProjectOrder = projectKeys => projectKeys,
|
|
39
40
|
} = context;
|
|
40
41
|
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
@@ -280,11 +281,49 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
280
281
|
);
|
|
281
282
|
}
|
|
282
283
|
|
|
284
|
+
function tmuxEntrySession(entry) {
|
|
285
|
+
return {
|
|
286
|
+
id: `tmux:${entry?.pane?.id || ""}`,
|
|
287
|
+
provider: entry?.agent?.provider || "",
|
|
288
|
+
status: "running",
|
|
289
|
+
title: entry?.agent?.title || entry?.tmuxSession?.name || "",
|
|
290
|
+
workspace: "",
|
|
291
|
+
originCwd: entry?.pane?.cwd || "",
|
|
292
|
+
cwd: entry?.pane?.cwd || "",
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function filteredLiveTmuxEntries(model, entries = liveTmuxEntries(state.snapshot && state.snapshot.tmux)) {
|
|
297
|
+
const sessionsById = new Map((state.snapshot?.sessions || []).map((session) => [String(session.id || ""), session]));
|
|
298
|
+
const modelSessionIds = new Set((model?.nodes || []).map((session) => String(session.id || "")));
|
|
299
|
+
const query = String(state.search || "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
300
|
+
return entries.filter((entry) => {
|
|
301
|
+
const linkedId = String(entry?.agent?.linkedSessionId || "");
|
|
302
|
+
const linkedSession = linkedId ? sessionsById.get(linkedId) : null;
|
|
303
|
+
if (linkedId && modelSessionIds.has(linkedId)) return false;
|
|
304
|
+
if (linkedSession && !isControlRoomSession(linkedSession)) return false;
|
|
305
|
+
const session = tmuxEntrySession(entry);
|
|
306
|
+
if (!session.originCwd || !matchesWorkspaceFilter(session)) return false;
|
|
307
|
+
if (!query) return true;
|
|
308
|
+
return [
|
|
309
|
+
session.title,
|
|
310
|
+
session.workspace,
|
|
311
|
+
session.originCwd,
|
|
312
|
+
entry?.distro?.name,
|
|
313
|
+
entry?.window?.name,
|
|
314
|
+
entry?.pane?.command,
|
|
315
|
+
entry?.agent?.command,
|
|
316
|
+
].join(" ").toLowerCase().includes(query);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
283
320
|
function liveTmuxPaneCard(entry) {
|
|
284
321
|
const { distro, tmuxSession, window, pane, agent } = entry;
|
|
285
322
|
const provider = providerInfo(agent.provider);
|
|
286
323
|
const linked = agent.linkedSessionId ? (state.snapshot?.sessions || []).find((session) => session.id === agent.linkedSessionId) || null : null;
|
|
287
|
-
const title = linked
|
|
324
|
+
const title = linked
|
|
325
|
+
? linked.title
|
|
326
|
+
: agent.title || pane.title || t("graph.tmux_task", { provider: provider.label });
|
|
288
327
|
const stateLabel = pane.dead ? t("graph.ended") : pane.active ? t("graph.selected_pane") : t("graph.background_running");
|
|
289
328
|
return `<article class="live-tmux-card ${pane.active ? "active" : ""} ${pane.dead ? "dead" : ""}"
|
|
290
329
|
style="${providerStyle(agent.provider)}"
|
|
@@ -525,50 +564,69 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
525
564
|
</article>`;
|
|
526
565
|
}
|
|
527
566
|
|
|
528
|
-
function runtimeSeparatedOverview(roots, model, allRoots = roots) {
|
|
567
|
+
function runtimeSeparatedOverview(roots, model, allRoots = roots, tmuxEntries = filteredLiveTmuxEntries(model)) {
|
|
529
568
|
const projectDescriptor = (root) => {
|
|
530
569
|
const project = controlRoomProject(root);
|
|
531
570
|
return { key: project.key, name: project.label };
|
|
532
571
|
};
|
|
572
|
+
const addGroupItem = (groups, key, name, field, value) => {
|
|
573
|
+
if (!groups.has(key)) groups.set(key, { name, roots: [], tmuxEntries: [] });
|
|
574
|
+
groups.get(key)[field].push(value);
|
|
575
|
+
};
|
|
533
576
|
const allGroups = new Map();
|
|
534
577
|
allRoots.forEach((root) => {
|
|
535
578
|
const { key, name } = projectDescriptor(root);
|
|
536
|
-
|
|
537
|
-
allGroups.get(key).roots.push(root);
|
|
579
|
+
addGroupItem(allGroups, key, name, "roots", root);
|
|
538
580
|
});
|
|
539
581
|
const groups = new Map();
|
|
540
582
|
roots.forEach((root) => {
|
|
541
583
|
const { key, name } = projectDescriptor(root);
|
|
542
|
-
|
|
543
|
-
|
|
584
|
+
addGroupItem(groups, key, name, "roots", root);
|
|
585
|
+
});
|
|
586
|
+
tmuxEntries.forEach((entry) => {
|
|
587
|
+
const { key, name } = projectDescriptor(tmuxEntrySession(entry));
|
|
588
|
+
addGroupItem(allGroups, key, name, "tmuxEntries", entry);
|
|
589
|
+
addGroupItem(groups, key, name, "tmuxEntries", entry);
|
|
544
590
|
});
|
|
545
591
|
const defaultOrderedGroups = [...groups.entries()].sort(([leftKey], [rightKey]) =>
|
|
546
|
-
Number(allGroups.get(rightKey)?.roots.length || 0)
|
|
592
|
+
Number((allGroups.get(rightKey)?.roots.length || 0) + (allGroups.get(rightKey)?.tmuxEntries.length || 0))
|
|
593
|
+
- Number((allGroups.get(leftKey)?.roots.length || 0) + (allGroups.get(leftKey)?.tmuxEntries.length || 0)));
|
|
547
594
|
const projectOrder = ensureProjectOrder(defaultOrderedGroups.map(([key]) => key));
|
|
548
595
|
const projectRank = new Map(projectOrder.map((key, index) => [key, index]));
|
|
549
596
|
const orderedGroups = defaultOrderedGroups.sort(([leftKey], [rightKey]) =>
|
|
550
597
|
Number(projectRank.get(leftKey) ?? Number.MAX_SAFE_INTEGER) - Number(projectRank.get(rightKey) ?? Number.MAX_SAFE_INTEGER));
|
|
551
|
-
const projectGroups = orderedGroups.map(([key, { name, roots: projectRoots }], index) => {
|
|
552
|
-
const projectTotals = allGroups.get(key)
|
|
553
|
-
const activeCount = projectTotals.filter((root) => isLiveSession(root)).length;
|
|
554
|
-
const attentionCount = projectTotals.filter((root) =>
|
|
598
|
+
const projectGroups = orderedGroups.map(([key, { name, roots: projectRoots, tmuxEntries: projectTmuxEntries }], index) => {
|
|
599
|
+
const projectTotals = allGroups.get(key) || { roots: projectRoots, tmuxEntries: projectTmuxEntries };
|
|
600
|
+
const activeCount = projectTotals.roots.filter((root) => isLiveSession(root)).length;
|
|
601
|
+
const attentionCount = projectTotals.roots.filter((root) =>
|
|
555
602
|
!isLiveSession(root)
|
|
556
603
|
&& isControlRoomSession(root)
|
|
557
604
|
&& ["waiting", "failed", "paused"].includes(root.status)).length;
|
|
558
|
-
const
|
|
605
|
+
const sessionSummary = attentionCount
|
|
559
606
|
? t("control.project_live_attention_summary", { active: activeCount, attention: attentionCount })
|
|
560
607
|
: t("control.project_live_summary", { active: activeCount });
|
|
608
|
+
const tmuxSummary = projectTotals.tmuxEntries.length
|
|
609
|
+
? t("control.project_tmux_summary", { count: projectTotals.tmuxEntries.length })
|
|
610
|
+
: "";
|
|
611
|
+
const summary = projectTotals.roots.length ? [sessionSummary, tmuxSummary].filter(Boolean).join(" · ") : tmuxSummary;
|
|
561
612
|
const disclosureKey = `control-project:${key}`;
|
|
562
613
|
const presentation = index === 0 ? "is-primary" : "is-secondary";
|
|
563
614
|
const projectFocusId = projectRoots[0]?.id || "";
|
|
615
|
+
const totalCount = projectTotals.roots.length + projectTotals.tmuxEntries.length;
|
|
616
|
+
const tmuxCards = projectTmuxEntries.length
|
|
617
|
+
? `<section class="control-project-tmux-section">
|
|
618
|
+
<header><span><i aria-hidden="true">▦</i><b>${esc(t("graph.tmux_session"))}</b></span><small>${esc(t("control.project_tmux_help"))}</small></header>
|
|
619
|
+
<div class="live-tmux-grid">${projectTmuxEntries.map((entry) => liveTmuxPaneCard(entry)).join("")}</div>
|
|
620
|
+
</section>`
|
|
621
|
+
: "";
|
|
564
622
|
return `<details class="control-room-project-group ${presentation}" data-control-project="${esc(name)}" data-project-sortable="${esc(key)}" data-disclosure-key="${esc(disclosureKey)}">
|
|
565
623
|
<summary class="control-project-header" data-project-toggle="${esc(name)}" draggable="true" aria-grabbed="false"
|
|
566
624
|
aria-keyshortcuts="Alt+ArrowUp Alt+ArrowDown" aria-label="${esc(t("project.drag_label", { name }))}" aria-describedby="projectReorderHelp">
|
|
567
|
-
<span class="control-project-heading"><i aria-hidden="true">□</i><b>${esc(name)}</b><small>${esc(summary)}</small><em>${
|
|
625
|
+
<span class="control-project-heading"><i aria-hidden="true">□</i><b>${esc(name)}</b><small>${esc(summary)}</small><em>${totalCount}</em></span>
|
|
568
626
|
<span class="control-project-handle" aria-hidden="true" title="${esc(t("project.reorder_hint"))}"></span>
|
|
569
627
|
</summary>
|
|
570
|
-
|
|
571
|
-
<div class="control-project-body">${projectRoots.map(root => controlRoomSession(root, model)).join("")}</div>
|
|
628
|
+
${projectFocusId ? `<button type="button" class="control-project-flow-link" data-graph-focus="${esc(projectFocusId)}"><span>${esc(t("control.open_full_flow"))} ↗</span></button>` : ""}
|
|
629
|
+
<div class="control-project-body">${projectRoots.map(root => controlRoomSession(root, model)).join("")}${tmuxCards}</div>
|
|
572
630
|
</details>`;
|
|
573
631
|
}).join("");
|
|
574
632
|
return `<div class="control-room-overview" data-control-room-overview="true">
|
|
@@ -892,7 +950,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
892
950
|
}
|
|
893
951
|
|
|
894
952
|
return {
|
|
895
|
-
graphNode, compactGraphNode, providerFlowLane, workflowCompactNode, liveTmuxEntries, liveTmuxPaneCard, runtimeSeparatedOverview,
|
|
953
|
+
graphNode, compactGraphNode, providerFlowLane, workflowCompactNode, liveTmuxEntries, tmuxEntrySession, filteredLiveTmuxEntries, liveTmuxPaneCard, runtimeSeparatedOverview,
|
|
896
954
|
controlRoomIntent, controlRoomSummary, controlRoomAgentGoal, inferredExecutionSummary,
|
|
897
955
|
workflowMetrics, workflowChildrenSummary, splitSubagents, completedSubagentDisclosure, agentPathTaskName, communicationEndpoint,
|
|
898
956
|
workflowCommunicationPanel, executionActivityLabel, executionActivityStatus, executionActivityCard, executionActivityPanel, focusedGraph,
|
|
@@ -168,7 +168,11 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
168
168
|
const graphLiveCount = showMap ? renderAgentMap(graphFilteredSessions(), motionKind) : 0;
|
|
169
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
|
-
const resultCount = attentionView
|
|
171
|
+
const resultCount = attentionView
|
|
172
|
+
? attentionCount
|
|
173
|
+
: state.view === "active"
|
|
174
|
+
? graphLiveCount
|
|
175
|
+
: regular.length;
|
|
172
176
|
$("#sessionResultSummary").textContent = window.LoadToAgentI18n.t("quality.results_summary", { count: resultCount });
|
|
173
177
|
const activeEmpty = state.view === "active" && graphLiveCount === 0;
|
|
174
178
|
$("#activeEmptyState").classList.toggle("hidden", !activeEmpty);
|