bosun 0.40.2 → 0.40.4

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.
@@ -5461,14 +5461,49 @@ select.input {
5461
5461
  radial-gradient(circle at 10% 20%, rgba(249, 115, 22, 0.12), transparent 40%),
5462
5462
  radial-gradient(circle at 85% 80%, rgba(56, 189, 248, 0.08), transparent 40%),
5463
5463
  var(--bg-card);
5464
- overflow: auto;
5465
- padding: 10px;
5464
+ overflow: hidden;
5465
+ padding: 8px;
5466
+ min-height: 460px;
5467
+ touch-action: none;
5468
+ cursor: grab;
5469
+ }
5470
+
5471
+ .task-dag-canvas-wrap.is-panning {
5472
+ cursor: grabbing;
5466
5473
  }
5467
5474
 
5468
5475
  .task-dag-canvas {
5469
5476
  width: 100%;
5470
5477
  min-width: 680px;
5471
- height: 420px;
5478
+ height: 460px;
5479
+ }
5480
+
5481
+ .task-dag-header-row {
5482
+ display: flex;
5483
+ flex-wrap: wrap;
5484
+ align-items: center;
5485
+ justify-content: space-between;
5486
+ gap: 8px;
5487
+ margin-bottom: 8px;
5488
+ }
5489
+
5490
+ .task-dag-controls {
5491
+ display: inline-flex;
5492
+ align-items: center;
5493
+ gap: 6px;
5494
+ flex-wrap: wrap;
5495
+ }
5496
+
5497
+ .task-dag-zoom-pill,
5498
+ .task-dag-wire-pill {
5499
+ display: inline-flex;
5500
+ align-items: center;
5501
+ border: 1px solid var(--border);
5502
+ border-radius: 999px;
5503
+ background: var(--bg-card-hover);
5504
+ padding: 3px 8px;
5505
+ font-size: 11px;
5506
+ color: var(--text-secondary);
5472
5507
  }
5473
5508
 
5474
5509
  .dag-node rect {
@@ -5480,6 +5515,11 @@ select.input {
5480
5515
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.35));
5481
5516
  }
5482
5517
 
5518
+ .dag-node-selected rect {
5519
+ stroke: var(--accent);
5520
+ filter: drop-shadow(0 0 0 2px rgba(56, 189, 248, 0.25));
5521
+ }
5522
+
5483
5523
  @media (max-width: 900px) {
5484
5524
  .task-comment-composer {
5485
5525
  grid-template-columns: 1fr;
@@ -195,9 +195,12 @@ body.kanban-dragging .kanban-card {
195
195
  background: var(--bg-card);
196
196
  border-radius: var(--radius-lg, 12px);
197
197
  padding: 14px;
198
- /* Use the Telegram stable viewport height, not raw 100vh which Telegram
199
- overrides prevents columns being too short or clipped on the web app */
200
- max-height: calc(var(--viewport-stable-height, 100vh) - 180px);
198
+ overflow: hidden;
199
+ /* Use the stable viewport height so each column keeps its own scroll area
200
+ without feeling too tall on desktop or cramped on mobile. */
201
+ height: clamp(360px, calc(var(--viewport-stable-height, 100vh) - 220px), 760px);
202
+ max-height: clamp(360px, calc(var(--viewport-stable-height, 100vh) - 220px), 760px);
203
+ min-height: 360px;
201
204
  position: relative;
202
205
  border: 1px solid var(--border);
203
206
  /* Limit layout recalculations to within the column — columns don't affect
@@ -289,13 +292,27 @@ body.kanban-dragging .kanban-card {
289
292
  flex-direction: column;
290
293
  gap: 8px;
291
294
  overflow-y: auto;
292
- flex: 1;
293
- padding: 4px;
295
+ overflow-x: hidden;
296
+ flex: 1 1 auto;
297
+ padding: 4px 4px 0;
294
298
  min-height: 0;
299
+ scrollbar-gutter: stable;
300
+ -webkit-overflow-scrolling: touch;
295
301
  touch-action: pan-y;
296
302
  overscroll-behavior-y: contain;
297
303
  }
298
304
 
305
+ .kanban-column-footer {
306
+ position: sticky;
307
+ bottom: 0;
308
+ flex-shrink: 0;
309
+ z-index: 2;
310
+ margin: 0 -14px -14px;
311
+ padding: 6px 14px 12px;
312
+ background: linear-gradient(to top, var(--bg-card) 72%, rgba(0, 0, 0, 0));
313
+ border-radius: 0 0 var(--radius-lg, 12px) var(--radius-lg, 12px);
314
+ }
315
+
299
316
  .kanban-cards > * {
300
317
  flex-shrink: 0;
301
318
  }
@@ -484,9 +501,9 @@ body.kanban-dragging .kanban-card {
484
501
  }
485
502
 
486
503
  .kanban-scroll-fade {
487
- height: 16px;
504
+ height: 10px;
488
505
  flex-shrink: 0;
489
- background: linear-gradient(to top, var(--bg-card), transparent);
506
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.08), transparent);
490
507
  border-radius: 0 0 var(--radius-lg, 12px) var(--radius-lg, 12px);
491
508
  pointer-events: none;
492
509
  margin: 0 -12px -12px;
@@ -506,7 +523,8 @@ body.kanban-dragging .kanban-card {
506
523
 
507
524
  .kanban-column {
508
525
  min-width: 280px;
509
- max-height: none;
526
+ height: clamp(420px, calc(var(--viewport-stable-height, 100vh) - 210px), 840px);
527
+ max-height: clamp(420px, calc(var(--viewport-stable-height, 100vh) - 210px), 840px);
510
528
  }
511
529
  }
512
530
 
@@ -523,6 +541,8 @@ body.kanban-dragging .kanban-card {
523
541
  flex: 0 0 85%;
524
542
  min-width: 260px;
525
543
  max-width: 85%;
544
+ height: clamp(380px, calc(var(--viewport-stable-height, 100vh) - 190px), 680px);
545
+ max-height: clamp(380px, calc(var(--viewport-stable-height, 100vh) - 190px), 680px);
526
546
  scroll-snap-align: start;
527
547
  scroll-snap-stop: always;
528
548
  }
@@ -539,6 +559,8 @@ body.kanban-dragging .kanban-card {
539
559
  .kanban-column {
540
560
  min-width: 86vw;
541
561
  max-width: 86vw;
562
+ height: clamp(360px, calc(var(--viewport-stable-height, 100vh) - 170px), 620px);
563
+ max-height: clamp(360px, calc(var(--viewport-stable-height, 100vh) - 170px), 620px);
542
564
  scroll-snap-align: start;
543
565
  padding: 12px;
544
566
  }
@@ -548,9 +570,12 @@ body.kanban-dragging .kanban-card {
548
570
  flex-direction: column;
549
571
  gap: 8px;
550
572
  overflow-y: auto;
551
- flex: 1;
573
+ overflow-x: hidden;
574
+ flex: 1 1 auto;
552
575
  padding: 4px;
553
576
  min-height: 0;
577
+ scrollbar-gutter: stable;
578
+ -webkit-overflow-scrolling: touch;
554
579
  touch-action: pan-y;
555
580
  overscroll-behavior-y: contain;
556
581
  }
@@ -582,6 +607,9 @@ body.kanban-dragging .kanban-card {
582
607
  }
583
608
 
584
609
  .kanban-column-head {
610
+ flex-shrink: 0;
611
+ position: relative;
612
+ z-index: 2;
585
613
  border-radius: calc(var(--radius-md, 8px) - 2px);
586
614
  background:
587
615
  linear-gradient(135deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0.01)),
@@ -595,12 +623,39 @@ body.kanban-dragging .kanban-card {
595
623
  }
596
624
 
597
625
  .kanban-load-more {
626
+ display: inline-flex;
627
+ align-items: center;
628
+ justify-content: center;
629
+ gap: 8px;
630
+ width: 100%;
631
+ min-height: 34px;
598
632
  text-align: center;
599
633
  font-size: 12px;
600
634
  color: var(--text-secondary);
601
- opacity: 0.8;
602
- padding: 8px 6px 12px;
635
+ opacity: 0.94;
636
+ padding: 8px 10px;
603
637
  letter-spacing: 0.01em;
638
+ background: rgba(255, 255, 255, 0.03);
639
+ border: 1px solid var(--border);
640
+ border-radius: 999px;
641
+ cursor: pointer;
642
+ font-family: inherit;
643
+ backdrop-filter: blur(10px);
644
+ }
645
+
646
+ .kanban-load-more:hover,
647
+ .kanban-load-more:focus-visible {
648
+ color: var(--text-primary);
649
+ }
650
+
651
+ .kanban-load-more:disabled {
652
+ cursor: progress;
653
+ opacity: 0.7;
654
+ }
655
+
656
+ .kanban-load-more-icon {
657
+ font-size: 15px;
658
+ line-height: 1;
604
659
  }
605
660
 
606
661
 
@@ -710,6 +710,75 @@ select.input {
710
710
  animation: slideUp 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
711
711
  }
712
712
 
713
+ .modal-content.modal-side-sheet {
714
+ width: min(100vw, var(--modal-sheet-width, 760px));
715
+ max-width: min(100vw, var(--modal-sheet-width, 760px));
716
+ height: 100dvh;
717
+ max-height: 100dvh;
718
+ margin: 0 0 0 auto;
719
+ border-radius: 24px 0 0 24px;
720
+ padding: 0;
721
+ overflow: hidden;
722
+ animation: slideInRight 0.24s ease;
723
+ position: relative;
724
+ }
725
+
726
+ .modal-side-sheet .modal-header {
727
+ position: sticky;
728
+ top: 0;
729
+ z-index: 3;
730
+ margin: 0;
731
+ padding: 18px 20px 14px;
732
+ border-bottom: 1px solid var(--border);
733
+ background: color-mix(in srgb, var(--bg-secondary) 92%, transparent);
734
+ backdrop-filter: blur(10px);
735
+ }
736
+
737
+ .modal-side-sheet .modal-title {
738
+ margin-bottom: 0;
739
+ padding-right: 36px;
740
+ }
741
+
742
+ .modal-side-sheet .modal-handle {
743
+ display: none;
744
+ }
745
+
746
+ .modal-side-sheet .modal-body {
747
+ height: calc(100dvh - 72px);
748
+ overflow: auto;
749
+ padding: 18px 20px calc(24px + var(--safe-bottom));
750
+ }
751
+
752
+ .modal-sheet-resizer {
753
+ position: absolute;
754
+ inset: 0 auto 0 0;
755
+ width: 12px;
756
+ cursor: ew-resize;
757
+ z-index: 4;
758
+ }
759
+
760
+ .modal-sheet-resizer::after {
761
+ content: "";
762
+ position: absolute;
763
+ left: 4px;
764
+ top: 18px;
765
+ bottom: 18px;
766
+ width: 2px;
767
+ border-radius: 999px;
768
+ background: linear-gradient(180deg, transparent, var(--border-strong), transparent);
769
+ }
770
+
771
+ @keyframes slideInRight {
772
+ from {
773
+ opacity: 0;
774
+ transform: translateX(24px);
775
+ }
776
+ to {
777
+ opacity: 1;
778
+ transform: translateX(0);
779
+ }
780
+ }
781
+
713
782
  .modal-handle {
714
783
  width: 36px;
715
784
  height: 4px;
@@ -1058,4 +1127,24 @@ select.input {
1058
1127
  .modal-overlay {
1059
1128
  align-items: center;
1060
1129
  }
1130
+
1131
+ .modal-content.modal-side-sheet {
1132
+ margin-bottom: 0;
1133
+ }
1134
+ }
1135
+
1136
+ @media (max-width: 900px) {
1137
+ .modal-content.modal-side-sheet {
1138
+ width: 100vw;
1139
+ max-width: 100vw;
1140
+ border-radius: 0;
1141
+ }
1142
+
1143
+ .modal-side-sheet .modal-body {
1144
+ height: calc(100dvh - 68px);
1145
+ }
1146
+
1147
+ .modal-sheet-resizer {
1148
+ display: none;
1149
+ }
1061
1150
  }
package/ui/tabs/agents.js CHANGED
@@ -126,6 +126,89 @@ function fleetThreadKey(thread, index) {
126
126
  return `thread-${index}:${taskKey}:${id}`;
127
127
  }
128
128
 
129
+ function buildSessionLogQueryParts(values = []) {
130
+ return Array.from(
131
+ new Set(
132
+ values
133
+ .map((value) => String(value || "").trim())
134
+ .filter(Boolean),
135
+ ),
136
+ );
137
+ }
138
+
139
+ function buildSessionLogQuery(values = []) {
140
+ return buildSessionLogQueryParts(values).join(" ");
141
+ }
142
+
143
+ function isSessionSignalAction(action = {}) {
144
+ const level = String(action?.level || "").toLowerCase();
145
+ const label = String(action?.label || "");
146
+ return (
147
+ level === "error"
148
+ || /(warn|error|failed|exception|timeout|anomal|retry|blocked|fatal)/i.test(label)
149
+ );
150
+ }
151
+
152
+ function buildSessionFocusedLogText(entry, rawLogText = "") {
153
+ const lines = [];
154
+ const session = entry?.session || null;
155
+ const insights = session?.insights || null;
156
+ const totals = insights?.totals || null;
157
+ const recentActions = Array.isArray(insights?.recentActions) ? insights.recentActions : [];
158
+ const signalActions = recentActions.filter(isSessionSignalAction);
159
+ const editedFiles = Array.isArray(insights?.activityDiff?.files) ? insights.activityDiff.files : [];
160
+ const queryParts = buildSessionLogQueryParts([
161
+ entry?.slot?.taskId,
162
+ entry?.session?.taskId,
163
+ entry?.session?.id,
164
+ entry?.slot?.branch,
165
+ entry?.session?.branch,
166
+ ]);
167
+
168
+ lines.push("Session-focused signals");
169
+ lines.push("=====================");
170
+ lines.push(
171
+ `Session: ${session?.title || session?.taskTitle || entry?.slot?.taskTitle || entry?.slot?.taskId || entry?.session?.id || "unknown"}`,
172
+ );
173
+ if (queryParts.length) {
174
+ lines.push(`Keys: ${queryParts.join(" | ")}`);
175
+ }
176
+ if (totals) {
177
+ lines.push(
178
+ `Totals: ${totals.messages || 0} messages, ${totals.toolCalls || 0} tool calls, ${totals.errors || 0} errors`,
179
+ );
180
+ }
181
+ if (signalActions.length) {
182
+ lines.push("");
183
+ lines.push("Warnings / errors");
184
+ lines.push("-----------------");
185
+ signalActions.slice(0, 8).forEach((action) => {
186
+ lines.push(`- [${String(action.timestamp || "").replace("T", " ").replace("Z", "")}] ${action.label}`);
187
+ });
188
+ }
189
+ if (editedFiles.length) {
190
+ lines.push("");
191
+ lines.push("Touched files");
192
+ lines.push("-------------");
193
+ editedFiles.slice(0, 8).forEach((file) => {
194
+ lines.push(`- ${file.path} (${file.edits || 0} edits)`);
195
+ });
196
+ }
197
+
198
+ const raw = String(rawLogText || "").trim();
199
+ if (raw) {
200
+ lines.push("");
201
+ lines.push("Raw focused log lines");
202
+ lines.push("---------------------");
203
+ lines.push(raw);
204
+ } else if (!signalActions.length) {
205
+ lines.push("");
206
+ lines.push("No session-specific warnings or log lines found yet.");
207
+ }
208
+
209
+ return lines.join("\n");
210
+ }
211
+
129
212
  /* ─── Workspace Viewer Modal ─── */
130
213
  function WorkspaceViewer({ agent, onClose }) {
131
214
  const [logText, setLogText] = useState("Loading…");
@@ -147,7 +230,11 @@ function WorkspaceViewer({ agent, onClose }) {
147
230
  const [expandedModelResponse, setExpandedModelResponse] = useState(false);
148
231
  const logRef = useRef(null);
149
232
 
150
- const query = agent.branch || agent.taskId || agent.sessionId || "";
233
+ const query = buildSessionLogQuery([
234
+ agent.sessionId,
235
+ agent.taskId,
236
+ agent.branch,
237
+ ]);
151
238
  const linkedSession =
152
239
  (sessionsData.value || []).find((s) => {
153
240
  if (!s) return false;
@@ -1146,7 +1233,7 @@ export function AgentsTab() {
1146
1233
  let active = true;
1147
1234
  const refreshTaskSessions = () => {
1148
1235
  if (!active) return;
1149
- loadSessions({ type: "task" });
1236
+ loadSessions({ type: "task", workspace: "all" });
1150
1237
  };
1151
1238
  refreshTaskSessions();
1152
1239
  const interval = setInterval(refreshTaskSessions, 5000);
@@ -1790,7 +1877,7 @@ function ContextViewer({ sessionId }) {
1790
1877
  }
1791
1878
 
1792
1879
  /* ─── Fleet Full Session View ─── */
1793
- function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
1880
+ function FleetSessionsPanel({ slots, taskFallbackEntries = [], onOpenWorkspace, onForceStop }) {
1794
1881
  const [detailTab, setDetailTab] = useState("stream");
1795
1882
  const [sessionScope, setSessionScope] = useState("active");
1796
1883
  const [selectedEntryKey, setSelectedEntryKey] = useState(null);
@@ -1802,7 +1889,7 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
1802
1889
  underlying data actually changes – prevents infinite render loops that
1803
1890
  previously caused "insertBefore" DOM errors. */
1804
1891
  const entries = useMemo(() => {
1805
- return (slots || [])
1892
+ const slotEntries = (slots || [])
1806
1893
  .map((slot, index) => {
1807
1894
  const session =
1808
1895
  allSessions.find((s) => s?.id && slot?.sessionId && s.id === slot.sessionId) ||
@@ -1819,7 +1906,51 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
1819
1906
  const bScore = new Date(b.slot?.startedAt || 0).getTime() || 0;
1820
1907
  return bScore - aScore;
1821
1908
  });
1822
- }, [slots, allSessions]);
1909
+
1910
+ const activeSessionIds = new Set(
1911
+ slotEntries.map((entry) => String(entry?.session?.id || "").trim()).filter(Boolean),
1912
+ );
1913
+ const activeTaskIds = new Set(
1914
+ slotEntries.map((entry) => String(entry?.slot?.taskId || entry?.session?.taskId || "").trim()).filter(Boolean),
1915
+ );
1916
+ const fallbackEntries = (taskFallbackEntries || [])
1917
+ .filter((task) => {
1918
+ const taskId = String(task?.id || task?.taskId || "").trim();
1919
+ if (!taskId) return false;
1920
+ if (activeTaskIds.has(taskId) || activeSessionIds.has(taskId)) return false;
1921
+ return true;
1922
+ })
1923
+ .map((task, index) => ({
1924
+ key: `task-${String(task?.id || task?.taskId || index).trim()}`,
1925
+ slot: {
1926
+ taskId: String(task?.id || task?.taskId || "").trim(),
1927
+ taskTitle: task?.title || task?.taskTitle || "(untitled task)",
1928
+ branch: task?.branchName || task?.branch || task?.meta?.branchName || "",
1929
+ baseBranch: task?.baseBranch || task?.meta?.baseBranch || null,
1930
+ status: task?.status || task?.runtimeSnapshot?.state || "idle",
1931
+ sessionId: String(task?.id || task?.taskId || "").trim(),
1932
+ startedAt:
1933
+ task?.lastActivityAt ||
1934
+ task?.updatedAt ||
1935
+ task?.createdAt ||
1936
+ null,
1937
+ synthetic: true,
1938
+ },
1939
+ index: (slots || []).length + index,
1940
+ session:
1941
+ allSessions.find((session) => {
1942
+ const sessionTaskId = String(session?.taskId || session?.id || "").trim();
1943
+ return sessionTaskId === String(task?.id || task?.taskId || "").trim();
1944
+ }) || null,
1945
+ isTaskFallback: true,
1946
+ }));
1947
+
1948
+ return [...slotEntries, ...fallbackEntries].sort((a, b) => {
1949
+ const aScore = new Date(a.slot?.startedAt || a.session?.lastActiveAt || 0).getTime() || 0;
1950
+ const bScore = new Date(b.slot?.startedAt || b.session?.lastActiveAt || 0).getTime() || 0;
1951
+ return bScore - aScore;
1952
+ });
1953
+ }, [slots, allSessions, taskFallbackEntries]);
1823
1954
 
1824
1955
  const historyEntries = useMemo(() => {
1825
1956
  const activeSessionIds = new Set(
@@ -1886,7 +2017,11 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
1886
2017
  visibleEntries.find((entry) => entry.key === selectedEntryKey)
1887
2018
  || visibleEntries[0]
1888
2019
  || null;
1889
- const sessionId = selectedEntry?.session?.id || null;
2020
+ const sessionId =
2021
+ selectedEntry?.session?.id
2022
+ || selectedEntry?.slot?.sessionId
2023
+ || selectedEntry?.slot?.taskId
2024
+ || null;
1890
2025
  const contextId = sessionId || selectedEntry?.slot?.taskId || null;
1891
2026
 
1892
2027
  useEffect(() => {
@@ -1894,12 +2029,14 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
1894
2029
  }, [sessionId]);
1895
2030
 
1896
2031
  useEffect(() => {
1897
- const query =
1898
- selectedEntry?.slot?.branch ||
1899
- selectedEntry?.slot?.taskId ||
1900
- selectedEntry?.session?.taskId ||
1901
- selectedEntry?.session?.id ||
1902
- "";
2032
+ const query = buildSessionLogQuery([
2033
+ selectedEntry?.session?.id,
2034
+ selectedEntry?.slot?.sessionId,
2035
+ selectedEntry?.slot?.taskId,
2036
+ selectedEntry?.session?.taskId,
2037
+ selectedEntry?.slot?.branch,
2038
+ selectedEntry?.session?.branch,
2039
+ ]);
1903
2040
  if (!query) {
1904
2041
  setLogText("(no logs yet)");
1905
2042
  return undefined;
@@ -1930,6 +2067,8 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
1930
2067
  };
1931
2068
  }, [selectedEntry?.key]);
1932
2069
 
2070
+ const focusedLogText = buildSessionFocusedLogText(selectedEntry, logText);
2071
+
1933
2072
  return html`
1934
2073
  <${Card}
1935
2074
  title="Fleet Session View"
@@ -1985,7 +2124,9 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
1985
2124
  <div class="fleet-slot-item-meta">
1986
2125
  ${entry.isHistory
1987
2126
  ? `Session ${entry.session?.id || "unknown"} · ${entry.session?.status || "unknown"}`
1988
- : `Slot ${(entry.index ?? 0) + 1} · ${entry.slot?.taskId || "no-task-id"}`}
2127
+ : entry.isTaskFallback
2128
+ ? `Task only · ${entry.slot?.status || "unknown"} · ${entry.slot?.taskId || "no-task-id"}`
2129
+ : `Slot ${(entry.index ?? 0) + 1} · ${entry.slot?.taskId || "no-task-id"}`}
1989
2130
  ${entry.isHistory && (entry.session?.lastActiveAt || entry.session?.updatedAt || entry.session?.createdAt)
1990
2131
  ? ` · ${formatRelative(entry.session?.lastActiveAt || entry.session?.updatedAt || entry.session?.createdAt)}`
1991
2132
  : ""}
@@ -2008,9 +2149,11 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
2008
2149
  </div>
2009
2150
  <div class="task-card-meta">
2010
2151
  ${selectedEntry.slot?.taskId || selectedEntry.session?.taskId || selectedEntry.session?.id || "?"}
2011
- ${selectedEntry.slot
2012
- ? ` · Slot ${(selectedEntry.index ?? 0) + 1}`
2013
- : ` · ${selectedEntry.session?.status || "history"}`}
2152
+ ${selectedEntry.isTaskFallback
2153
+ ? ` · ${selectedEntry.slot?.status || "task"}`
2154
+ : selectedEntry.slot
2155
+ ? ` · Slot ${(selectedEntry.index ?? 0) + 1}`
2156
+ : ` · ${selectedEntry.session?.status || "history"}`}
2014
2157
  ${selectedEntry.slot?.branch
2015
2158
  ? ` · ${selectedEntry.slot.branch}`
2016
2159
  : selectedEntry.session?.branch
@@ -2018,7 +2161,7 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
2018
2161
  : ""}
2019
2162
  </div>
2020
2163
  </div>
2021
- ${selectedEntry.slot && html`
2164
+ ${selectedEntry.slot && !selectedEntry.isTaskFallback && html`
2022
2165
  <div class="btn-row">
2023
2166
  <${Button} variant="text" size="small" onClick=${() => onOpenWorkspace(selectedEntry.slot, selectedEntry.index)}>
2024
2167
  ${iconText(":search: Workspace")}
@@ -2068,7 +2211,10 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
2068
2211
  `
2069
2212
  : detailTab === "diff"
2070
2213
  ? sessionId
2071
- ? html`<${DiffViewer} sessionId=${sessionId} />`
2214
+ ? html`<${DiffViewer}
2215
+ sessionId=${sessionId}
2216
+ activitySummary=${selectedEntry?.session?.insights?.activityDiff || null}
2217
+ />`
2072
2218
  : html`
2073
2219
  <div class="chat-view chat-empty-state">
2074
2220
  <div class="session-empty-icon">${resolveIcon(":edit:")}</div>
@@ -2076,7 +2222,7 @@ function FleetSessionsPanel({ slots, onOpenWorkspace, onForceStop }) {
2076
2222
  </div>
2077
2223
  `
2078
2224
  : detailTab === "logs"
2079
- ? html`<div class="workspace-log fleet-session-log" ref=${logRef}>${logText}</div>`
2225
+ ? html`<div class="workspace-log fleet-session-log" ref=${logRef}>${focusedLogText}</div>`
2080
2226
  : null}
2081
2227
  </div>
2082
2228
  `
@@ -2097,12 +2243,13 @@ export function FleetSessionsTab() {
2097
2243
  const executor = executorData.value;
2098
2244
  const execData = executor?.data;
2099
2245
  const slots = execData?.slots || [];
2246
+ const [taskFallbackEntries, setTaskFallbackEntries] = useState([]);
2100
2247
 
2101
2248
  useEffect(() => {
2102
2249
  let active = true;
2103
2250
  const refreshTaskSessions = () => {
2104
2251
  if (!active) return;
2105
- loadSessions({ type: "task" });
2252
+ loadSessions({ type: "task", workspace: "all" });
2106
2253
  };
2107
2254
  refreshTaskSessions();
2108
2255
  const interval = setInterval(refreshTaskSessions, 5000);
@@ -2112,6 +2259,32 @@ export function FleetSessionsTab() {
2112
2259
  };
2113
2260
  }, []);
2114
2261
 
2262
+ useEffect(() => {
2263
+ let active = true;
2264
+ const loadFallbackTasks = () => {
2265
+ if (!active) return;
2266
+ apiFetch("/api/tasks?limit=1000", { _silent: true })
2267
+ .then((res) => {
2268
+ if (!active) return;
2269
+ const rows = Array.isArray(res?.data) ? res.data : [];
2270
+ const filtered = rows.filter((task) => {
2271
+ const status = String(task?.status || "").trim().toLowerCase();
2272
+ return status === "inprogress" || status === "inreview";
2273
+ });
2274
+ setTaskFallbackEntries(filtered);
2275
+ })
2276
+ .catch(() => {
2277
+ if (active) setTaskFallbackEntries([]);
2278
+ });
2279
+ };
2280
+ loadFallbackTasks();
2281
+ const interval = setInterval(loadFallbackTasks, 5000);
2282
+ return () => {
2283
+ active = false;
2284
+ clearInterval(interval);
2285
+ };
2286
+ }, []);
2287
+
2115
2288
  /* Force stop a specific agent slot */
2116
2289
  const handleForceStop = async (slot) => {
2117
2290
  const ok = await showConfirm(
@@ -2143,6 +2316,7 @@ export function FleetSessionsTab() {
2143
2316
  <div class="fleet-span">
2144
2317
  <${FleetSessionsPanel}
2145
2318
  slots=${slots}
2319
+ taskFallbackEntries=${taskFallbackEntries}
2146
2320
  onOpenWorkspace=${openWorkspace}
2147
2321
  onForceStop=${handleForceStop}
2148
2322
  />