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.
- package/README.md +4 -0
- package/cli.mjs +127 -69
- package/config/config.mjs +35 -15
- package/desktop/package.json +2 -2
- package/infra/monitor.mjs +52 -16
- package/infra/session-tracker.mjs +1 -0
- package/infra/sync-engine.mjs +6 -1
- package/infra/update-check.mjs +16 -10
- package/kanban/kanban-adapter.mjs +19 -4
- package/kanban/ve-orchestrator.ps1 +25 -0
- package/package.json +1 -1
- package/server/ui-server.mjs +502 -39
- package/task/task-executor.mjs +690 -6
- package/task/task-store.mjs +116 -1
- package/ui/components/kanban-board.js +137 -9
- package/ui/components/shared.js +107 -45
- package/ui/demo-defaults.js +20 -20
- package/ui/demo.html +26 -1
- package/ui/modules/mui.js +600 -397
- package/ui/styles/components.css +43 -3
- package/ui/styles/kanban.css +66 -11
- package/ui/styles.monolith.css +89 -0
- package/ui/tabs/agents.js +194 -20
- package/ui/tabs/tasks.js +673 -162
- package/workflow/workflow-engine.mjs +30 -29
- package/workflow/workflow-nodes.mjs +321 -22
- package/workflow/workflow-templates.mjs +1 -1
- package/workflow-templates/task-batch.mjs +10 -10
- package/workspace/workspace-manager.mjs +25 -0
- package/workspace/worktree-manager.mjs +8 -2
package/ui/styles/components.css
CHANGED
|
@@ -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:
|
|
5465
|
-
padding:
|
|
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:
|
|
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;
|
package/ui/styles/kanban.css
CHANGED
|
@@ -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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
293
|
-
|
|
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:
|
|
504
|
+
height: 10px;
|
|
488
505
|
flex-shrink: 0;
|
|
489
|
-
background: linear-gradient(to top,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
602
|
-
padding: 8px
|
|
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
|
|
package/ui/styles.monolith.css
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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?.
|
|
1899
|
-
selectedEntry?.slot?.
|
|
1900
|
-
selectedEntry?.
|
|
1901
|
-
selectedEntry?.session?.
|
|
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
|
-
:
|
|
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.
|
|
2012
|
-
? ` ·
|
|
2013
|
-
:
|
|
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}
|
|
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}>${
|
|
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
|
/>
|