bosun 0.36.0 → 0.36.2
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/.env.example +98 -16
- package/README.md +27 -0
- package/agent-event-bus.mjs +5 -5
- package/agent-pool.mjs +129 -12
- package/agent-prompts.mjs +7 -1
- package/agent-sdk.mjs +13 -2
- package/agent-supervisor.mjs +2 -2
- package/agent-work-report.mjs +1 -1
- package/anomaly-detector.mjs +6 -6
- package/autofix.mjs +15 -15
- package/bosun-skills.mjs +4 -4
- package/bosun.schema.json +160 -4
- package/claude-shell.mjs +11 -11
- package/cli.mjs +21 -21
- package/codex-config.mjs +19 -19
- package/codex-shell.mjs +180 -29
- package/config-doctor.mjs +27 -2
- package/config.mjs +60 -7
- package/copilot-shell.mjs +4 -4
- package/error-detector.mjs +1 -1
- package/fleet-coordinator.mjs +2 -2
- package/gemini-shell.mjs +692 -0
- package/github-oauth-portal.mjs +1 -1
- package/github-reconciler.mjs +2 -2
- package/kanban-adapter.mjs +741 -168
- package/merge-strategy.mjs +25 -25
- package/monitor.mjs +123 -105
- package/opencode-shell.mjs +22 -22
- package/package.json +7 -1
- package/postinstall.mjs +22 -22
- package/pr-cleanup-daemon.mjs +6 -6
- package/prepublish-check.mjs +4 -4
- package/presence.mjs +2 -2
- package/primary-agent.mjs +85 -7
- package/publish.mjs +1 -1
- package/review-agent.mjs +1 -1
- package/session-tracker.mjs +11 -0
- package/setup-web-server.mjs +429 -21
- package/setup.mjs +367 -12
- package/shared-knowledge.mjs +1 -1
- package/startup-service.mjs +9 -9
- package/stream-resilience.mjs +58 -4
- package/sync-engine.mjs +2 -2
- package/task-assessment.mjs +9 -9
- package/task-cli.mjs +1 -1
- package/task-complexity.mjs +71 -2
- package/task-context.mjs +1 -2
- package/task-executor.mjs +104 -41
- package/telegram-bot.mjs +825 -494
- package/telegram-sentinel.mjs +28 -28
- package/ui/app.js +256 -23
- package/ui/app.monolith.js +1 -1
- package/ui/components/agent-selector.js +4 -3
- package/ui/components/chat-view.js +101 -28
- package/ui/components/diff-viewer.js +3 -3
- package/ui/components/kanban-board.js +3 -3
- package/ui/components/session-list.js +255 -35
- package/ui/components/workspace-switcher.js +3 -3
- package/ui/demo.html +209 -194
- package/ui/index.html +3 -3
- package/ui/modules/icon-utils.js +206 -142
- package/ui/modules/icons.js +2 -27
- package/ui/modules/settings-schema.js +29 -5
- package/ui/modules/streaming.js +30 -2
- package/ui/modules/vision-stream.js +275 -0
- package/ui/modules/voice-client.js +102 -9
- package/ui/modules/voice-fallback.js +62 -6
- package/ui/modules/voice-overlay.js +594 -59
- package/ui/modules/voice.js +31 -38
- package/ui/setup.html +284 -34
- package/ui/styles/components.css +47 -0
- package/ui/styles/sessions.css +75 -0
- package/ui/tabs/agents.js +73 -43
- package/ui/tabs/chat.js +37 -40
- package/ui/tabs/control.js +2 -2
- package/ui/tabs/dashboard.js +1 -1
- package/ui/tabs/infra.js +10 -10
- package/ui/tabs/library.js +8 -8
- package/ui/tabs/logs.js +10 -10
- package/ui/tabs/settings.js +20 -20
- package/ui/tabs/tasks.js +76 -47
- package/ui-server.mjs +1761 -124
- package/update-check.mjs +13 -13
- package/ve-kanban.mjs +1 -1
- package/whatsapp-channel.mjs +5 -5
- package/workflow-engine.mjs +20 -1
- package/workflow-nodes.mjs +904 -4
- package/workflow-templates/agents.mjs +321 -7
- package/workflow-templates/ci-cd.mjs +6 -6
- package/workflow-templates/github.mjs +156 -84
- package/workflow-templates/planning.mjs +8 -8
- package/workflow-templates/reliability.mjs +8 -8
- package/workflow-templates/security.mjs +3 -3
- package/workflow-templates.mjs +15 -9
- package/workspace-manager.mjs +85 -1
- package/workspace-monitor.mjs +2 -2
- package/workspace-registry.mjs +2 -2
- package/worktree-manager.mjs +1 -1
|
@@ -91,36 +91,153 @@ function normalizePreview(content) {
|
|
|
91
91
|
return text.slice(0, 100);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function canonicalMessageKind(msg) {
|
|
95
|
+
const role = String(msg?.role || "").trim().toLowerCase();
|
|
96
|
+
const type = String(msg?.type || "").trim().toLowerCase();
|
|
97
|
+
if (
|
|
98
|
+
role === "assistant" ||
|
|
99
|
+
type === "agent_message" ||
|
|
100
|
+
type === "assistant" ||
|
|
101
|
+
type === "assistant_message"
|
|
102
|
+
) {
|
|
103
|
+
return "assistant";
|
|
104
|
+
}
|
|
105
|
+
if (role === "user" || type === "user") return "user";
|
|
106
|
+
if (type === "tool_call") return "tool_call";
|
|
107
|
+
if (type === "tool_result" || type === "tool_output") return "tool_result";
|
|
108
|
+
if (type === "error" || type === "stream_error") return "error";
|
|
109
|
+
if (role === "system" || type === "system") return "system";
|
|
110
|
+
return `${role || "unknown"}:${type || "message"}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function messageBody(msg) {
|
|
114
|
+
const value = msg?.content ?? msg?.text ?? "";
|
|
115
|
+
return String(value || "").trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isLifecycleSystemMessage(msg) {
|
|
119
|
+
if (canonicalMessageKind(msg) !== "system") return false;
|
|
120
|
+
const lifecycle = String(msg?.meta?.lifecycle || "").trim().toLowerCase();
|
|
121
|
+
if (lifecycle) return true;
|
|
122
|
+
const content = messageBody(msg).toLowerCase();
|
|
123
|
+
if (!content) return true;
|
|
124
|
+
return (
|
|
125
|
+
content === "turn completed" ||
|
|
126
|
+
content === "session completed" ||
|
|
127
|
+
content === "agent is composing a response..." ||
|
|
128
|
+
content === "agent is composing a response…" ||
|
|
129
|
+
content.startsWith("message_stop") ||
|
|
130
|
+
content.startsWith("message_delta")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function reconnectFingerprint(content) {
|
|
135
|
+
const text = String(content || "").trim();
|
|
136
|
+
if (!text) return "";
|
|
137
|
+
const lower = text.toLowerCase();
|
|
138
|
+
if (!lower.includes("stream disconnected")) return "";
|
|
139
|
+
return lower
|
|
140
|
+
.replace(/reconnecting\.\.\.\s*\d+\s*\/\s*\d+/g, "reconnecting... n/n")
|
|
141
|
+
.replace(/\s+/g, " ")
|
|
142
|
+
.trim();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isDecorativeLine(text) {
|
|
146
|
+
const compact = String(text || "").replace(/\s+/g, "");
|
|
147
|
+
if (!compact) return true;
|
|
148
|
+
if (/^[\-=_*`~.·•]+$/.test(compact)) return true;
|
|
149
|
+
if (/^[\u2500-\u257f]+$/u.test(compact)) return true;
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
94
153
|
function dedupeMessages(messages) {
|
|
95
154
|
const list = Array.isArray(messages) ? messages : [];
|
|
96
155
|
const out = [];
|
|
97
156
|
const seenExact = new Set();
|
|
157
|
+
const recentAssistantContentTs = new Map();
|
|
158
|
+
const reconnectIndexByFingerprint = new Map();
|
|
98
159
|
for (const msg of list) {
|
|
99
160
|
if (!msg) continue;
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
161
|
+
const kind = canonicalMessageKind(msg);
|
|
162
|
+
const content = messageBody(msg);
|
|
163
|
+
if (!content && kind !== "user") continue;
|
|
164
|
+
if (isLifecycleSystemMessage(msg)) continue;
|
|
165
|
+
if (kind === "system" && isDecorativeLine(content)) continue;
|
|
166
|
+
if (
|
|
167
|
+
kind === "assistant" &&
|
|
168
|
+
content.length <= 2 &&
|
|
169
|
+
!/[a-z0-9]/i.test(content)
|
|
170
|
+
) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
103
173
|
const ts = Date.parse(msg.timestamp || 0) || 0;
|
|
104
|
-
const exactKey = `${
|
|
174
|
+
const exactKey = `${kind}|${content}|${ts}`;
|
|
105
175
|
if (seenExact.has(exactKey)) continue;
|
|
176
|
+
const reconnectKey = kind === "error" ? reconnectFingerprint(content) : "";
|
|
177
|
+
const normalizedMsg =
|
|
178
|
+
reconnectKey && !msg.id ? { ...msg, id: `reconnect:${reconnectKey}` } : msg;
|
|
179
|
+
if (reconnectKey) {
|
|
180
|
+
const existingIndex = reconnectIndexByFingerprint.get(reconnectKey);
|
|
181
|
+
if (Number.isInteger(existingIndex) && existingIndex >= 0 && existingIndex < out.length) {
|
|
182
|
+
const existing = out[existingIndex];
|
|
183
|
+
out[existingIndex] = existing?.id
|
|
184
|
+
? { ...normalizedMsg, id: existing.id }
|
|
185
|
+
: normalizedMsg;
|
|
186
|
+
seenExact.add(exactKey);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (kind === "assistant" && content) {
|
|
191
|
+
while (out.length > 0 && isLifecycleSystemMessage(out[out.length - 1])) {
|
|
192
|
+
out.pop();
|
|
193
|
+
}
|
|
194
|
+
const lastAssistant = out[out.length - 1];
|
|
195
|
+
if (lastAssistant && canonicalMessageKind(lastAssistant) === "assistant") {
|
|
196
|
+
const lastTs = Date.parse(lastAssistant.timestamp || 0) || 0;
|
|
197
|
+
const withinStreamingWindow =
|
|
198
|
+
ts > 0 && lastTs > 0 ? Math.abs(ts - lastTs) <= 120000 : true;
|
|
199
|
+
if (withinStreamingWindow) {
|
|
200
|
+
out[out.length - 1] = lastAssistant?.id
|
|
201
|
+
? { ...normalizedMsg, id: lastAssistant.id }
|
|
202
|
+
: normalizedMsg;
|
|
203
|
+
recentAssistantContentTs.set(content, ts);
|
|
204
|
+
seenExact.add(exactKey);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const prevAssistantTs = recentAssistantContentTs.get(content);
|
|
209
|
+
if (prevAssistantTs !== undefined) {
|
|
210
|
+
const withinAssistantWindow =
|
|
211
|
+
ts > 0 && prevAssistantTs > 0
|
|
212
|
+
? Math.abs(ts - prevAssistantTs) <= 5000
|
|
213
|
+
: true;
|
|
214
|
+
if (withinAssistantWindow) continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
106
217
|
const last = out[out.length - 1];
|
|
107
218
|
if (last) {
|
|
108
|
-
const
|
|
109
|
-
const lastType = String(last.type || "");
|
|
219
|
+
const lastKind = canonicalMessageKind(last);
|
|
110
220
|
const lastContent = String(last.content || last.text || "").trim();
|
|
111
221
|
const lastTs = Date.parse(last.timestamp || 0) || 0;
|
|
222
|
+
const withinDuplicateWindow =
|
|
223
|
+
ts > 0 && lastTs > 0 ? Math.abs(ts - lastTs) <= 5000 : true;
|
|
112
224
|
if (
|
|
113
225
|
content &&
|
|
114
|
-
|
|
115
|
-
lastType === type &&
|
|
226
|
+
lastKind === kind &&
|
|
116
227
|
lastContent === content &&
|
|
117
|
-
|
|
228
|
+
withinDuplicateWindow
|
|
118
229
|
) {
|
|
119
230
|
continue;
|
|
120
231
|
}
|
|
121
232
|
}
|
|
122
233
|
seenExact.add(exactKey);
|
|
123
|
-
out.push(
|
|
234
|
+
out.push(normalizedMsg);
|
|
235
|
+
if (reconnectKey) {
|
|
236
|
+
reconnectIndexByFingerprint.set(reconnectKey, out.length - 1);
|
|
237
|
+
}
|
|
238
|
+
if (kind === "assistant" && content) {
|
|
239
|
+
recentAssistantContentTs.set(content, ts);
|
|
240
|
+
}
|
|
124
241
|
}
|
|
125
242
|
return out;
|
|
126
243
|
}
|
|
@@ -296,6 +413,32 @@ const STATUS_COLOR_MAP = {
|
|
|
296
413
|
archived: "var(--text-hint)",
|
|
297
414
|
};
|
|
298
415
|
|
|
416
|
+
const SESSION_VIEW_FILTER = Object.freeze({
|
|
417
|
+
all: "all",
|
|
418
|
+
active: "active",
|
|
419
|
+
historic: "historic",
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
function normalizeSessionViewFilter(value) {
|
|
423
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
424
|
+
if (normalized === SESSION_VIEW_FILTER.active) return SESSION_VIEW_FILTER.active;
|
|
425
|
+
if (normalized === SESSION_VIEW_FILTER.historic) return SESSION_VIEW_FILTER.historic;
|
|
426
|
+
return SESSION_VIEW_FILTER.all;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function getSessionStatusKey(session) {
|
|
430
|
+
return String(session?.status || "idle").trim().toLowerCase();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function isActiveSession(session) {
|
|
434
|
+
const status = getSessionStatusKey(session);
|
|
435
|
+
return status === "active" || status === "running";
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function isHistoricSession(session) {
|
|
439
|
+
return !isActiveSession(session);
|
|
440
|
+
}
|
|
441
|
+
|
|
299
442
|
/* ─── Swipeable Session Item ─── */
|
|
300
443
|
function SwipeableSessionItem({
|
|
301
444
|
session: s,
|
|
@@ -406,7 +549,7 @@ function SwipeableSessionItem({
|
|
|
406
549
|
onClick=${handleResume}
|
|
407
550
|
title="Unarchive"
|
|
408
551
|
>
|
|
409
|
-
<span class="session-action-icon"
|
|
552
|
+
<span class="session-action-icon">:workflow:</span>
|
|
410
553
|
<span class="session-action-label">Restore</span>
|
|
411
554
|
</button>
|
|
412
555
|
`
|
|
@@ -416,7 +559,7 @@ function SwipeableSessionItem({
|
|
|
416
559
|
onClick=${handleArchive}
|
|
417
560
|
title="Archive session"
|
|
418
561
|
>
|
|
419
|
-
<span class="session-action-icon">${resolveIcon("
|
|
562
|
+
<span class="session-action-icon">${resolveIcon(":box:")}</span>
|
|
420
563
|
<span class="session-action-label">Archive</span>
|
|
421
564
|
</button>
|
|
422
565
|
`}
|
|
@@ -425,7 +568,7 @@ function SwipeableSessionItem({
|
|
|
425
568
|
onClick=${handleDelete}
|
|
426
569
|
title=${confirmDelete ? "Confirm delete" : "Delete session"}
|
|
427
570
|
>
|
|
428
|
-
<span class="session-action-icon">${resolveIcon(confirmDelete ? "
|
|
571
|
+
<span class="session-action-icon">${resolveIcon(confirmDelete ? ":alert:" : ":trash:")}</span>
|
|
429
572
|
<span class="session-action-label">${confirmDelete ? "Sure?" : "Delete"}</span>
|
|
430
573
|
</button>
|
|
431
574
|
</div>
|
|
@@ -522,6 +665,8 @@ export function SessionList({
|
|
|
522
665
|
onSelect,
|
|
523
666
|
showArchived = true,
|
|
524
667
|
onToggleArchived,
|
|
668
|
+
sessionView = SESSION_VIEW_FILTER.all,
|
|
669
|
+
onSessionViewChange,
|
|
525
670
|
defaultType = null,
|
|
526
671
|
renamingSessionId = null,
|
|
527
672
|
onStartRename,
|
|
@@ -530,9 +675,36 @@ export function SessionList({
|
|
|
530
675
|
}) {
|
|
531
676
|
const [search, setSearch] = useState("");
|
|
532
677
|
const [revealedActions, setRevealedActions] = useState(null);
|
|
678
|
+
const [uncontrolledSessionView, setUncontrolledSessionView] = useState(
|
|
679
|
+
normalizeSessionViewFilter(sessionView),
|
|
680
|
+
);
|
|
533
681
|
const allSessions = sessionsData.value || [];
|
|
534
682
|
const error = sessionsError.value;
|
|
535
683
|
const hasSearch = search.trim().length > 0;
|
|
684
|
+
const resolvedSessionView =
|
|
685
|
+
typeof onSessionViewChange === "function"
|
|
686
|
+
? normalizeSessionViewFilter(sessionView)
|
|
687
|
+
: uncontrolledSessionView;
|
|
688
|
+
|
|
689
|
+
useEffect(() => {
|
|
690
|
+
if (typeof onSessionViewChange === "function") return;
|
|
691
|
+
const normalized = normalizeSessionViewFilter(sessionView);
|
|
692
|
+
if (normalized !== uncontrolledSessionView) {
|
|
693
|
+
setUncontrolledSessionView(normalized);
|
|
694
|
+
}
|
|
695
|
+
}, [onSessionViewChange, sessionView, uncontrolledSessionView]);
|
|
696
|
+
|
|
697
|
+
const setSessionView = useCallback(
|
|
698
|
+
(nextFilter) => {
|
|
699
|
+
const normalized = normalizeSessionViewFilter(nextFilter);
|
|
700
|
+
if (typeof onSessionViewChange === "function") {
|
|
701
|
+
onSessionViewChange(normalized);
|
|
702
|
+
} else {
|
|
703
|
+
setUncontrolledSessionView(normalized);
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
[onSessionViewChange],
|
|
707
|
+
);
|
|
536
708
|
|
|
537
709
|
// Filter by defaultType to exclude ghost sessions (e.g. task sessions in Chat tab)
|
|
538
710
|
const typeFiltered = defaultType
|
|
@@ -549,30 +721,39 @@ export function SessionList({
|
|
|
549
721
|
})
|
|
550
722
|
: allSessions;
|
|
551
723
|
|
|
552
|
-
const
|
|
724
|
+
const archivedFiltered = showArchived
|
|
553
725
|
? typeFiltered
|
|
554
|
-
: typeFiltered.filter((s) => s
|
|
726
|
+
: typeFiltered.filter((s) => getSessionStatusKey(s) !== "archived");
|
|
727
|
+
|
|
728
|
+
const viewFiltered = archivedFiltered.filter((s) => {
|
|
729
|
+
if (resolvedSessionView === SESSION_VIEW_FILTER.active) {
|
|
730
|
+
return isActiveSession(s);
|
|
731
|
+
}
|
|
732
|
+
if (resolvedSessionView === SESSION_VIEW_FILTER.historic) {
|
|
733
|
+
return isHistoricSession(s);
|
|
734
|
+
}
|
|
735
|
+
return true;
|
|
736
|
+
});
|
|
555
737
|
|
|
556
738
|
const filtered = search
|
|
557
|
-
?
|
|
739
|
+
? viewFiltered.filter(
|
|
558
740
|
(s) =>
|
|
559
741
|
(s.title || "").toLowerCase().includes(search.toLowerCase()) ||
|
|
560
742
|
(s.taskId || "").toLowerCase().includes(search.toLowerCase()),
|
|
561
743
|
)
|
|
562
|
-
:
|
|
744
|
+
: viewFiltered;
|
|
563
745
|
|
|
564
|
-
const active = filtered.filter(
|
|
565
|
-
|
|
566
|
-
);
|
|
567
|
-
const archived = filtered.filter((s) => s.status === "archived");
|
|
746
|
+
const active = filtered.filter((s) => isActiveSession(s));
|
|
747
|
+
const archived = filtered.filter((s) => getSessionStatusKey(s) === "archived");
|
|
568
748
|
const recent = filtered.filter(
|
|
569
749
|
(s) =>
|
|
570
|
-
s
|
|
571
|
-
s.status !== "running" &&
|
|
572
|
-
s.status !== "archived",
|
|
750
|
+
!isActiveSession(s) && getSessionStatusKey(s) !== "archived",
|
|
573
751
|
);
|
|
574
752
|
|
|
575
|
-
const archivedCount = typeFiltered.filter((s) => s
|
|
753
|
+
const archivedCount = typeFiltered.filter((s) => getSessionStatusKey(s) === "archived").length;
|
|
754
|
+
const allCount = archivedFiltered.length;
|
|
755
|
+
const activeCount = archivedFiltered.filter((s) => isActiveSession(s)).length;
|
|
756
|
+
const historicCount = archivedFiltered.filter((s) => isHistoricSession(s)).length;
|
|
576
757
|
|
|
577
758
|
const handleSelect = useCallback(
|
|
578
759
|
(id) => {
|
|
@@ -588,6 +769,13 @@ export function SessionList({
|
|
|
588
769
|
loadSessions(_lastLoadFilter);
|
|
589
770
|
}, []);
|
|
590
771
|
|
|
772
|
+
const handleCreateSession = useCallback(() => {
|
|
773
|
+
if (resolvedSessionView === SESSION_VIEW_FILTER.historic) {
|
|
774
|
+
setSessionView(SESSION_VIEW_FILTER.all);
|
|
775
|
+
}
|
|
776
|
+
createSession(defaultType ? { type: defaultType } : {});
|
|
777
|
+
}, [defaultType, resolvedSessionView, setSessionView]);
|
|
778
|
+
|
|
591
779
|
const handleArchive = useCallback(async (id) => {
|
|
592
780
|
setRevealedActions(null);
|
|
593
781
|
await archiveSession(id);
|
|
@@ -609,6 +797,21 @@ export function SessionList({
|
|
|
609
797
|
setRevealedActions(null);
|
|
610
798
|
}, []);
|
|
611
799
|
|
|
800
|
+
const emptyTitle = hasSearch
|
|
801
|
+
? "No matching sessions"
|
|
802
|
+
: resolvedSessionView === SESSION_VIEW_FILTER.active
|
|
803
|
+
? "No active sessions"
|
|
804
|
+
: resolvedSessionView === SESSION_VIEW_FILTER.historic
|
|
805
|
+
? "No historic sessions"
|
|
806
|
+
: "No sessions yet";
|
|
807
|
+
const emptyHint = hasSearch
|
|
808
|
+
? "Try a different keyword or clear the search."
|
|
809
|
+
: resolvedSessionView === SESSION_VIEW_FILTER.active
|
|
810
|
+
? "Start a new session or switch to All."
|
|
811
|
+
: resolvedSessionView === SESSION_VIEW_FILTER.historic
|
|
812
|
+
? "Historic sessions appear after they finish."
|
|
813
|
+
: "Create a session to get started.";
|
|
814
|
+
|
|
612
815
|
/* ── Render session items ── */
|
|
613
816
|
function renderSessionItem(s) {
|
|
614
817
|
return html`
|
|
@@ -637,7 +840,7 @@ export function SessionList({
|
|
|
637
840
|
<span class="session-list-title">Sessions</span>
|
|
638
841
|
</div>
|
|
639
842
|
<div class="session-empty">
|
|
640
|
-
<div class="session-empty-icon">${resolveIcon("
|
|
843
|
+
<div class="session-empty-icon">${resolveIcon(":server:")}</div>
|
|
641
844
|
<div class="session-empty-text">Sessions not available</div>
|
|
642
845
|
<button class="btn btn-primary btn-sm" onClick=${handleRetry}>
|
|
643
846
|
Retry
|
|
@@ -666,8 +869,7 @@ export function SessionList({
|
|
|
666
869
|
`}
|
|
667
870
|
<button
|
|
668
871
|
class="btn btn-primary btn-sm"
|
|
669
|
-
onClick=${
|
|
670
|
-
createSession(defaultType ? { type: defaultType } : {})}
|
|
872
|
+
onClick=${handleCreateSession}
|
|
671
873
|
>
|
|
672
874
|
+ New
|
|
673
875
|
</button>
|
|
@@ -683,6 +885,27 @@ export function SessionList({
|
|
|
683
885
|
/>
|
|
684
886
|
</div>
|
|
685
887
|
|
|
888
|
+
<div style="display:flex;gap:6px;flex-wrap:wrap;padding:0 10px 8px;">
|
|
889
|
+
<button
|
|
890
|
+
class="btn btn-sm ${resolvedSessionView === SESSION_VIEW_FILTER.all ? "btn-primary" : "btn-ghost"}"
|
|
891
|
+
onClick=${() => setSessionView(SESSION_VIEW_FILTER.all)}
|
|
892
|
+
>
|
|
893
|
+
All (${allCount})
|
|
894
|
+
</button>
|
|
895
|
+
<button
|
|
896
|
+
class="btn btn-sm ${resolvedSessionView === SESSION_VIEW_FILTER.active ? "btn-primary" : "btn-ghost"}"
|
|
897
|
+
onClick=${() => setSessionView(SESSION_VIEW_FILTER.active)}
|
|
898
|
+
>
|
|
899
|
+
Active (${activeCount})
|
|
900
|
+
</button>
|
|
901
|
+
<button
|
|
902
|
+
class="btn btn-sm ${resolvedSessionView === SESSION_VIEW_FILTER.historic ? "btn-primary" : "btn-ghost"}"
|
|
903
|
+
onClick=${() => setSessionView(SESSION_VIEW_FILTER.historic)}
|
|
904
|
+
>
|
|
905
|
+
Historic (${historicCount})
|
|
906
|
+
</button>
|
|
907
|
+
</div>
|
|
908
|
+
|
|
686
909
|
<div class="session-list-scroll">
|
|
687
910
|
${active.length > 0 &&
|
|
688
911
|
html`
|
|
@@ -702,20 +925,17 @@ export function SessionList({
|
|
|
702
925
|
${filtered.length === 0 &&
|
|
703
926
|
html`
|
|
704
927
|
<div class="session-empty">
|
|
705
|
-
<div class="session-empty-icon">${resolveIcon("
|
|
928
|
+
<div class="session-empty-icon">${resolveIcon(":chat:")}</div>
|
|
706
929
|
<div class="session-empty-text">
|
|
707
|
-
${
|
|
930
|
+
${emptyTitle}
|
|
708
931
|
<div class="session-empty-subtext">
|
|
709
|
-
${
|
|
710
|
-
? "Try a different keyword or clear the search."
|
|
711
|
-
: "Create a session to get started."}
|
|
932
|
+
${emptyHint}
|
|
712
933
|
</div>
|
|
713
934
|
</div>
|
|
714
935
|
<div class="session-empty-actions">
|
|
715
936
|
<button
|
|
716
937
|
class="btn btn-primary btn-sm"
|
|
717
|
-
onClick=${
|
|
718
|
-
createSession(defaultType ? { type: defaultType } : {})}
|
|
938
|
+
onClick=${handleCreateSession}
|
|
719
939
|
>
|
|
720
940
|
+ New Session
|
|
721
941
|
</button>
|
|
@@ -312,12 +312,12 @@ function WorkspaceCard({ ws }) {
|
|
|
312
312
|
onClick=${handlePull}
|
|
313
313
|
disabled=${pulling}
|
|
314
314
|
title="Pull all repos"
|
|
315
|
-
>${pulling ? html`<${Spinner} /> Pulling` : iconText("
|
|
315
|
+
>${pulling ? html`<${Spinner} /> Pulling` : iconText(":refresh: Pull")}</button>
|
|
316
316
|
<button
|
|
317
317
|
class="ws-manager-btn ghost sm danger-text"
|
|
318
318
|
onClick=${() => { haptic("light"); setDelConfirm(true); }}
|
|
319
319
|
title="Delete workspace"
|
|
320
|
-
>${resolveIcon("
|
|
320
|
+
>${resolveIcon(":trash:")}</button>
|
|
321
321
|
</div>
|
|
322
322
|
</div>
|
|
323
323
|
|
|
@@ -425,7 +425,7 @@ export function WorkspaceManager({ open, onClose }) {
|
|
|
425
425
|
onClick=${handleScan}
|
|
426
426
|
disabled=${scanning}
|
|
427
427
|
title="Scan disk for workspaces"
|
|
428
|
-
>${scanning ? "Scanning…" : iconText("
|
|
428
|
+
>${scanning ? "Scanning…" : iconText(":search: Scan Disk")}</button>
|
|
429
429
|
</div>
|
|
430
430
|
|
|
431
431
|
${loading && !wsList.length
|