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.
@@ -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 openDrawer(id) {
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
- clearTimeout(motionState.drawerTimer);
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(() => $("#closeDrawerBtn").focus({ preventScroll: true }), 0);
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
- clearTimeout(motionState.drawerTimer);
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(() => $("#closeDrawerBtn").focus({ preventScroll: true }), 0);
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
- clearTimeout(motionState.drawerTimer);
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
- setDialogOpenState($("#detailDrawer"), false);
91
- $("#drawerBackdrop").classList.add("closing");
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, { conversation: true }) : "";
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 { openDrawer, openSubagentConversation, openExecutionActivity, closeDrawer, renderDrawer };
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 route = event.target.closest("[data-agent-command-route]");
170
- if (route) {
171
- state.agentCommandRoutes.set(route.dataset.agentCommandSession, route.dataset.agentCommandRoute);
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(() => $("#detailDrawer")?.querySelector(`[data-agent-command-session="${CSS.escape(route.dataset.agentCommandSession)}"][data-agent-command-route="${CSS.escape(route.dataset.agentCommandRoute)}"]`)?.focus({ preventScroll: true }));
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", scheduleAgentWorkflowConnections);
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, liveTmuxEntries(state.snapshot && state.snapshot.tmux));
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 ? linked.title : pane.title || t("graph.tmux_task", { provider: provider.label });
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
- if (!allGroups.has(key)) allGroups.set(key, { name, roots: [] });
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
- if (!groups.has(key)) groups.set(key, { name, roots: [] });
543
- groups.get(key).roots.push(root);
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) - Number(allGroups.get(leftKey)?.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)?.roots || projectRoots;
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 summary = attentionCount
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>${projectTotals.length}</em></span>
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
- <button type="button" class="control-project-flow-link" data-graph-focus="${esc(projectFocusId)}"><span>${esc(t("control.open_full_flow"))} ↗</span></button>
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 ? attentionCount : graphLiveCount + regular.length;
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);