bosun 0.31.0 → 0.31.3
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/monitor.mjs +3 -0
- package/package.json +1 -1
- package/ui/components/shared.js +3 -1
- package/ui/tabs/agents.js +0 -3
- package/ui/tabs/settings.js +76 -47
- package/ui-server.mjs +15 -2
package/monitor.mjs
CHANGED
|
@@ -9372,6 +9372,7 @@ function formatCodexResult(result) {
|
|
|
9372
9372
|
}
|
|
9373
9373
|
if (typeof result === "object") {
|
|
9374
9374
|
const candidates = [
|
|
9375
|
+
result.finalResponse,
|
|
9375
9376
|
result.output,
|
|
9376
9377
|
result.text,
|
|
9377
9378
|
result.message,
|
|
@@ -12748,6 +12749,8 @@ export {
|
|
|
12748
12749
|
appendKnowledgeEntry,
|
|
12749
12750
|
buildKnowledgeEntry,
|
|
12750
12751
|
formatKnowledgeSummary,
|
|
12752
|
+
extractPlannerTasksFromOutput,
|
|
12753
|
+
formatCodexResult,
|
|
12751
12754
|
// Container runner re-exports
|
|
12752
12755
|
getContainerStatus,
|
|
12753
12756
|
isContainerEnabled,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.3",
|
|
4
4
|
"description": "AI-powered orchestrator supervisor — manages AI agent executors with failover, auto-restarts on failure, analyzes crashes with Codex SDK, creates PRs via Vibe-Kanban API, and sends Telegram notifications. Supports N executors with weighted distribution, multi-repo projects, and auto-setup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache 2.0",
|
package/ui/components/shared.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* ────────────────────────────────────────────────────────────── */
|
|
5
5
|
|
|
6
6
|
import { h } from "preact";
|
|
7
|
+
import { createPortal } from "preact/compat";
|
|
7
8
|
import {
|
|
8
9
|
useState,
|
|
9
10
|
useEffect,
|
|
@@ -319,7 +320,7 @@ export function Modal({ title, open = true, onClose, children, contentClassName
|
|
|
319
320
|
? `transform: translateY(${dragY}px); opacity: ${Math.max(0.2, 1 - dragY / 400)}`
|
|
320
321
|
: "";
|
|
321
322
|
|
|
322
|
-
|
|
323
|
+
const content = html`
|
|
323
324
|
<div
|
|
324
325
|
class="modal-overlay ${visible ? "modal-overlay-visible" : ""}"
|
|
325
326
|
onClick=${(e) => {
|
|
@@ -354,6 +355,7 @@ export function Modal({ title, open = true, onClose, children, contentClassName
|
|
|
354
355
|
</div>
|
|
355
356
|
</div>
|
|
356
357
|
`;
|
|
358
|
+
return createPortal(content, document.body);
|
|
357
359
|
}
|
|
358
360
|
|
|
359
361
|
/* ═══════════════════════════════════════════════
|
package/ui/tabs/agents.js
CHANGED
package/ui/tabs/settings.js
CHANGED
|
@@ -90,26 +90,44 @@ const SETTINGS_STYLES = `
|
|
|
90
90
|
/* Floating save bar */
|
|
91
91
|
.settings-save-bar {
|
|
92
92
|
position: fixed;
|
|
93
|
-
bottom: calc(var(--nav-height) + var(--safe-bottom) +
|
|
94
|
-
left:
|
|
95
|
-
|
|
93
|
+
bottom: calc(var(--nav-height, 60px) + var(--safe-bottom, 0px) + 12px);
|
|
94
|
+
left: 50%;
|
|
95
|
+
transform: translateX(-50%);
|
|
96
|
+
z-index: 999;
|
|
96
97
|
display: flex;
|
|
97
98
|
align-items: center;
|
|
98
99
|
justify-content: space-between;
|
|
99
100
|
gap: 12px;
|
|
100
101
|
flex-wrap: wrap;
|
|
101
102
|
row-gap: 8px;
|
|
102
|
-
padding:
|
|
103
|
-
|
|
103
|
+
padding: 10px 16px;
|
|
104
|
+
min-width: 240px;
|
|
105
|
+
max-width: 480px;
|
|
106
|
+
width: auto;
|
|
104
107
|
background: var(--glass-bg, rgba(30,30,46,0.95));
|
|
105
108
|
backdrop-filter: blur(20px);
|
|
106
109
|
-webkit-backdrop-filter: blur(20px);
|
|
107
|
-
border
|
|
110
|
+
border: 1px solid var(--border, rgba(255,255,255,0.08));
|
|
111
|
+
border-radius: 12px;
|
|
112
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
113
|
+
transition: all 0.2s ease;
|
|
108
114
|
animation: slideUp 0.25s ease;
|
|
109
115
|
}
|
|
116
|
+
.settings-save-bar--dirty {
|
|
117
|
+
border-color: var(--accent, #5b6eae);
|
|
118
|
+
box-shadow: 0 4px 20px rgba(91, 110, 174, 0.25);
|
|
119
|
+
}
|
|
120
|
+
.settings-save-bar--clean .save-bar-info {
|
|
121
|
+
color: var(--text-hint, #666);
|
|
122
|
+
font-size: 12px;
|
|
123
|
+
}
|
|
124
|
+
.setting-modified-dot--clean {
|
|
125
|
+
background: var(--text-hint, #666) !important;
|
|
126
|
+
opacity: 0.4;
|
|
127
|
+
}
|
|
110
128
|
@keyframes slideUp {
|
|
111
|
-
from { transform:
|
|
112
|
-
to { transform: translateY(0); opacity: 1; }
|
|
129
|
+
from { transform: translateX(-50%) translateY(20px); opacity: 0; }
|
|
130
|
+
to { transform: translateX(-50%) translateY(0); opacity: 1; }
|
|
113
131
|
}
|
|
114
132
|
.settings-save-bar .save-bar-info {
|
|
115
133
|
display: flex;
|
|
@@ -129,7 +147,7 @@ const SETTINGS_STYLES = `
|
|
|
129
147
|
}
|
|
130
148
|
@media (min-width: 1200px) {
|
|
131
149
|
.settings-save-bar {
|
|
132
|
-
bottom:
|
|
150
|
+
bottom: 16px;
|
|
133
151
|
}
|
|
134
152
|
}
|
|
135
153
|
/* Individual setting row */
|
|
@@ -359,6 +377,7 @@ const SETTINGS_STYLES = `
|
|
|
359
377
|
margin-right: auto;
|
|
360
378
|
width: 100%;
|
|
361
379
|
box-sizing: border-box;
|
|
380
|
+
padding-bottom: 80px;
|
|
362
381
|
}
|
|
363
382
|
|
|
364
383
|
body.settings-save-open .main-content {
|
|
@@ -1069,15 +1088,14 @@ function ServerConfigMode() {
|
|
|
1069
1088
|
<//>
|
|
1070
1089
|
`}
|
|
1071
1090
|
|
|
1072
|
-
<!-- Floating save bar -->
|
|
1073
|
-
${changeCount > 0
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
<
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
<div class="save-bar-actions">
|
|
1091
|
+
<!-- Floating save bar - always visible -->
|
|
1092
|
+
<div class=${`settings-save-bar ${changeCount > 0 ? 'settings-save-bar--dirty' : 'settings-save-bar--clean'}`}>
|
|
1093
|
+
<div class="save-bar-info">
|
|
1094
|
+
<span class=${`setting-modified-dot ${changeCount === 0 ? 'setting-modified-dot--clean' : ''}`}></span>
|
|
1095
|
+
<span>${changeCount > 0 ? `${changeCount} unsaved change${changeCount !== 1 ? "s" : ""}` : "All changes saved"}</span>
|
|
1096
|
+
</div>
|
|
1097
|
+
<div class="save-bar-actions">
|
|
1098
|
+
${changeCount > 0 && html`
|
|
1081
1099
|
<button class="btn btn-ghost btn-sm" onClick=${handleDiscard}>
|
|
1082
1100
|
Discard
|
|
1083
1101
|
</button>
|
|
@@ -1088,9 +1106,9 @@ function ServerConfigMode() {
|
|
|
1088
1106
|
>
|
|
1089
1107
|
${saving ? html`<${Spinner} size=${14} /> Saving…` : "Save Changes"}
|
|
1090
1108
|
</button>
|
|
1091
|
-
|
|
1109
|
+
`}
|
|
1092
1110
|
</div>
|
|
1093
|
-
|
|
1111
|
+
</div>
|
|
1094
1112
|
|
|
1095
1113
|
<!-- Confirm dialog with diff -->
|
|
1096
1114
|
${confirmOpen &&
|
|
@@ -1176,33 +1194,38 @@ function AppPreferencesMode() {
|
|
|
1176
1194
|
/* Load prefs from CloudStorage on mount */
|
|
1177
1195
|
useEffect(() => {
|
|
1178
1196
|
(async () => {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
+
try {
|
|
1198
|
+
const [fs, ct, nu, ne, nc, dm, dmp, ds, dr] = await Promise.all([
|
|
1199
|
+
cloudGet("fontSize"),
|
|
1200
|
+
cloudGet("colorTheme"),
|
|
1201
|
+
cloudGet("notifyUpdates"),
|
|
1202
|
+
cloudGet("notifyErrors"),
|
|
1203
|
+
cloudGet("notifyComplete"),
|
|
1204
|
+
cloudGet("debugMode"),
|
|
1205
|
+
cloudGet("defaultMaxParallel"),
|
|
1206
|
+
cloudGet("defaultSdk"),
|
|
1207
|
+
cloudGet("defaultRegion"),
|
|
1208
|
+
]);
|
|
1209
|
+
if (fs) {
|
|
1210
|
+
setFontSize(fs);
|
|
1211
|
+
applyFontSize(fs);
|
|
1212
|
+
}
|
|
1213
|
+
if (ct) {
|
|
1214
|
+
setColorTheme(ct);
|
|
1215
|
+
applyColorTheme(ct);
|
|
1216
|
+
}
|
|
1217
|
+
if (nu != null) setNotifyUpdates(nu);
|
|
1218
|
+
if (ne != null) setNotifyErrors(ne);
|
|
1219
|
+
if (nc != null) setNotifyComplete(nc);
|
|
1220
|
+
if (dm != null) setDebugMode(dm);
|
|
1221
|
+
if (dmp != null) setDefaultMaxParallel(dmp);
|
|
1222
|
+
if (ds) setDefaultSdk(ds);
|
|
1223
|
+
if (dr) setDefaultRegion(dr);
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
console.warn('[AppPrefs] Failed to load preferences:', err);
|
|
1226
|
+
} finally {
|
|
1227
|
+
setLoaded(true);
|
|
1197
1228
|
}
|
|
1198
|
-
if (nu != null) setNotifyUpdates(nu);
|
|
1199
|
-
if (ne != null) setNotifyErrors(ne);
|
|
1200
|
-
if (nc != null) setNotifyComplete(nc);
|
|
1201
|
-
if (dm != null) setDebugMode(dm);
|
|
1202
|
-
if (dmp != null) setDefaultMaxParallel(dmp);
|
|
1203
|
-
if (ds) setDefaultSdk(ds);
|
|
1204
|
-
if (dr) setDefaultRegion(dr);
|
|
1205
|
-
setLoaded(true);
|
|
1206
1229
|
})();
|
|
1207
1230
|
}, []);
|
|
1208
1231
|
|
|
@@ -1211,6 +1234,7 @@ function AppPreferencesMode() {
|
|
|
1211
1234
|
const next = !getter;
|
|
1212
1235
|
setter(next);
|
|
1213
1236
|
cloudSet(key, next);
|
|
1237
|
+
console.log('[AppPrefs] Saved:', key, next);
|
|
1214
1238
|
haptic();
|
|
1215
1239
|
showToast("Preference saved", "success");
|
|
1216
1240
|
}, []);
|
|
@@ -1218,6 +1242,7 @@ function AppPreferencesMode() {
|
|
|
1218
1242
|
const handleFontSize = (v) => {
|
|
1219
1243
|
setFontSize(v);
|
|
1220
1244
|
cloudSet("fontSize", v);
|
|
1245
|
+
console.log('[AppPrefs] Saved: fontSize', v);
|
|
1221
1246
|
haptic();
|
|
1222
1247
|
applyFontSize(v);
|
|
1223
1248
|
showToast("Font size saved", "success");
|
|
@@ -1226,6 +1251,7 @@ function AppPreferencesMode() {
|
|
|
1226
1251
|
const handleColorTheme = (v) => {
|
|
1227
1252
|
setColorTheme(v);
|
|
1228
1253
|
cloudSet("colorTheme", v);
|
|
1254
|
+
console.log('[AppPrefs] Saved: colorTheme', v);
|
|
1229
1255
|
haptic();
|
|
1230
1256
|
applyColorTheme(v);
|
|
1231
1257
|
showToast("Theme saved", "success");
|
|
@@ -1235,6 +1261,7 @@ function AppPreferencesMode() {
|
|
|
1235
1261
|
const val = Math.max(1, Math.min(20, Number(v)));
|
|
1236
1262
|
setDefaultMaxParallel(val);
|
|
1237
1263
|
cloudSet("defaultMaxParallel", val);
|
|
1264
|
+
console.log('[AppPrefs] Saved: defaultMaxParallel', val);
|
|
1238
1265
|
haptic();
|
|
1239
1266
|
showToast("Preference saved", "success");
|
|
1240
1267
|
};
|
|
@@ -1242,6 +1269,7 @@ function AppPreferencesMode() {
|
|
|
1242
1269
|
const handleDefaultSdk = (v) => {
|
|
1243
1270
|
setDefaultSdk(v);
|
|
1244
1271
|
cloudSet("defaultSdk", v);
|
|
1272
|
+
console.log('[AppPrefs] Saved: defaultSdk', v);
|
|
1245
1273
|
haptic();
|
|
1246
1274
|
showToast("Preference saved", "success");
|
|
1247
1275
|
};
|
|
@@ -1249,6 +1277,7 @@ function AppPreferencesMode() {
|
|
|
1249
1277
|
const handleDefaultRegion = (v) => {
|
|
1250
1278
|
setDefaultRegion(v);
|
|
1251
1279
|
cloudSet("defaultRegion", v);
|
|
1280
|
+
console.log('[AppPrefs] Saved: defaultRegion', v);
|
|
1252
1281
|
haptic();
|
|
1253
1282
|
showToast("Preference saved", "success");
|
|
1254
1283
|
};
|
|
@@ -1312,7 +1341,7 @@ function AppPreferencesMode() {
|
|
|
1312
1341
|
: "";
|
|
1313
1342
|
|
|
1314
1343
|
return html`
|
|
1315
|
-
|
|
1344
|
+
|
|
1316
1345
|
|
|
1317
1346
|
<!-- ─── Account ─── -->
|
|
1318
1347
|
<${Collapsible} title="👤 Account" defaultOpen=${true}>
|
package/ui-server.mjs
CHANGED
|
@@ -546,6 +546,8 @@ let uiServer = null;
|
|
|
546
546
|
let uiServerUrl = null;
|
|
547
547
|
let uiServerTls = false;
|
|
548
548
|
let wsServer = null;
|
|
549
|
+
/** Auto-open browser: only once per process, never during tests */
|
|
550
|
+
let _browserOpened = false;
|
|
549
551
|
const wsClients = new Set();
|
|
550
552
|
let sessionListenerAttached = false;
|
|
551
553
|
/** @type {ReturnType<typeof setInterval>|null} */
|
|
@@ -5468,8 +5470,19 @@ export async function startTelegramUiServer(options = {}) {
|
|
|
5468
5470
|
console.log(`[telegram-ui] LAN access: ${protocol}://${lanIp}:${actualPort}`);
|
|
5469
5471
|
console.log(`[telegram-ui] Browser access: ${protocol}://${lanIp}:${actualPort}/?token=${sessionToken}`);
|
|
5470
5472
|
|
|
5471
|
-
// Auto-open browser
|
|
5472
|
-
|
|
5473
|
+
// Auto-open browser:
|
|
5474
|
+
// - skip in desktop/Electron mode (BOSUN_DESKTOP=1)
|
|
5475
|
+
// - skip when caller passes skipAutoOpen
|
|
5476
|
+
// - skip during Vitest / Jest test runs (avoids opening 20+ tabs during `npm test`)
|
|
5477
|
+
// - only open ONCE per process (singleton guard — prevents loops on server restart)
|
|
5478
|
+
const isTestRun = process.env.VITEST || process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID;
|
|
5479
|
+
if (
|
|
5480
|
+
process.env.BOSUN_DESKTOP !== "1" &&
|
|
5481
|
+
!options.skipAutoOpen &&
|
|
5482
|
+
!_browserOpened &&
|
|
5483
|
+
!isTestRun
|
|
5484
|
+
) {
|
|
5485
|
+
_browserOpened = true;
|
|
5473
5486
|
const openUrl = `${protocol}://${lanIp}:${actualPort}/?token=${sessionToken}`;
|
|
5474
5487
|
try {
|
|
5475
5488
|
const { exec } = await import("node:child_process");
|