bosun 0.41.0 → 0.41.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 +8 -0
- package/README.md +20 -0
- package/agent/agent-event-bus.mjs +248 -6
- package/agent/agent-pool.mjs +125 -28
- package/agent/agent-work-analyzer.mjs +8 -16
- package/agent/retry-queue.mjs +164 -0
- package/bosun.config.example.json +25 -0
- package/bosun.schema.json +825 -183
- package/cli.mjs +59 -5
- package/config/config.mjs +130 -3
- package/infra/monitor.mjs +693 -67
- package/infra/runtime-accumulator.mjs +376 -84
- package/infra/session-tracker.mjs +82 -25
- package/lib/codebase-audit.mjs +133 -18
- package/package.json +23 -4
- package/server/setup-web-server.mjs +25 -0
- package/server/ui-server.mjs +248 -29
- package/setup.mjs +27 -24
- package/shell/codex-shell.mjs +34 -3
- package/shell/copilot-shell.mjs +50 -8
- package/task/msg-hub.mjs +193 -0
- package/task/pipeline.mjs +544 -0
- package/task/task-cli.mjs +38 -2
- package/task/task-executor-pipeline.mjs +143 -0
- package/task/task-executor.mjs +36 -27
- package/telegram/get-telegram-chat-id.mjs +57 -47
- package/ui/components/workspace-switcher.js +7 -7
- package/ui/demo-defaults.js +15694 -10573
- package/ui/modules/settings-schema.js +2 -0
- package/ui/modules/state.js +54 -57
- package/ui/modules/voice-client-sdk.js +375 -36
- package/ui/modules/voice-client.js +140 -31
- package/ui/setup.html +68 -2
- package/ui/styles/components.css +57 -0
- package/ui/styles.css +201 -1
- package/ui/tabs/dashboard.js +74 -0
- package/ui/tabs/logs.js +10 -0
- package/ui/tabs/settings.js +178 -99
- package/ui/tabs/tasks.js +31 -1
- package/ui/tabs/telemetry.js +34 -0
- package/ui/tabs/workflow-canvas-utils.mjs +8 -1
- package/ui/tabs/workflows.js +532 -275
- package/voice/voice-agents-sdk.mjs +1 -1
- package/voice/voice-relay.mjs +6 -6
- package/workflow/declarative-workflows.mjs +145 -0
- package/workflow/msg-hub.mjs +237 -0
- package/workflow/pipeline-workflows.mjs +287 -0
- package/workflow/pipeline.mjs +828 -315
- package/workflow/workflow-cli.mjs +128 -0
- package/workflow/workflow-engine.mjs +329 -17
- package/workflow/workflow-nodes/custom-loader.mjs +250 -0
- package/workflow/workflow-nodes.mjs +1955 -223
- package/workflow/workflow-templates.mjs +26 -8
- package/workflow-templates/agents.mjs +0 -1
- package/workflow-templates/bosun-native.mjs +212 -2
- package/workflow-templates/continuation-loop.mjs +339 -0
- package/workflow-templates/github.mjs +516 -40
- package/workflow-templates/planning.mjs +446 -17
- package/workflow-templates/reliability.mjs +65 -12
- package/workflow-templates/task-batch.mjs +24 -8
- package/workflow-templates/task-lifecycle.mjs +83 -6
- package/workspace/context-cache.mjs +66 -18
- package/workspace/workspace-manager.mjs +2 -1
- package/workflow-templates/issue-continuation.mjs +0 -243
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* @property {string} [unit] - Display unit (e.g., 'ms', 'min', 'sec')
|
|
20
20
|
* @property {boolean} [restart] - If true, changing requires process restart
|
|
21
21
|
* @property {boolean} [advanced] - If true, hidden unless "Show advanced" is on
|
|
22
|
+
* @property {string} [section] - Optional UI subsection label within a category
|
|
22
23
|
*/
|
|
23
24
|
|
|
24
25
|
export const CATEGORIES = [
|
|
@@ -248,6 +249,7 @@ export const SETTINGS_SCHEMA = [
|
|
|
248
249
|
{ key: "SELF_RESTART_WATCH_ENABLED", label: "Self-Restart Watcher", category: "advanced", type: "boolean", description: "Auto-restart when source files change. Defaults to true in devmode." },
|
|
249
250
|
{ key: "MAX_PARALLEL", label: "Global Max Parallel", category: "advanced", type: "number", defaultVal: 6, min: 1, max: 50, description: "Global maximum parallel task slots across all executors." },
|
|
250
251
|
{ key: "RESTART_DELAY_MS", label: "Restart Delay", category: "advanced", type: "number", defaultVal: 10000, min: 1000, max: 60000, unit: "ms", description: "Delay before restarting after a crash." },
|
|
252
|
+
{ key: "ENV_RELOAD_DELAY_MS", label: "Settings Reload Delay", category: "advanced", type: "number", defaultVal: 5000, min: 500, max: 60000, unit: "ms", description: "Quiet period before Bosun reloads runtime config after .env or settings changes. Higher values make restart-sensitive saves less abrupt." },
|
|
251
253
|
{ key: "SHARED_STATE_ENABLED", label: "Shared State", category: "advanced", type: "boolean", defaultVal: true, description: "Enable distributed task coordination for multi-instance setups." },
|
|
252
254
|
{ key: "WORKFLOW_AUTOMATION_ENABLED", label: "Workflow Automation", category: "advanced", type: "boolean", defaultVal: true, description: "Enable event-driven workflow auto-triggers from monitor events." },
|
|
253
255
|
{ key: "SHARED_STATE_STALE_THRESHOLD_MS", label: "Stale Threshold", category: "advanced", type: "number", defaultVal: 300000, min: 60000, max: 1800000, unit: "ms", description: "Time before a heartbeat is considered stale.", advanced: true },
|
package/ui/modules/state.js
CHANGED
|
@@ -54,6 +54,7 @@ const CACHE_TTL = {
|
|
|
54
54
|
benchmarks: 8000,
|
|
55
55
|
telemetry: 15000,
|
|
56
56
|
analytics: 30000,
|
|
57
|
+
"retry-queue": 5000,
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
function _cacheKey(url) { return url; }
|
|
@@ -158,11 +159,31 @@ export const tasksStatusCounts = signal({ draft: 0, backlog: 0, inProgress: 0, i
|
|
|
158
159
|
export const retryQueueData = signal({ count: 0, items: [] });
|
|
159
160
|
export const retryQueueLoaded = signal(false);
|
|
160
161
|
|
|
162
|
+
function normalizeRetryQueuePayload(payload) {
|
|
163
|
+
return {
|
|
164
|
+
count: Number(payload?.count || 0),
|
|
165
|
+
items: Array.isArray(payload?.items) ? payload.items : [],
|
|
166
|
+
stats: payload?.stats && typeof payload.stats === "object"
|
|
167
|
+
? {
|
|
168
|
+
totalRetriesToday: Number(payload.stats.totalRetriesToday || 0),
|
|
169
|
+
peakRetryDepth: Number(payload.stats.peakRetryDepth || 0),
|
|
170
|
+
exhaustedTaskIds: Array.isArray(payload.stats.exhaustedTaskIds) ? payload.stats.exhaustedTaskIds : [],
|
|
171
|
+
}
|
|
172
|
+
: {
|
|
173
|
+
totalRetriesToday: 0,
|
|
174
|
+
peakRetryDepth: 0,
|
|
175
|
+
exhaustedTaskIds: [],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
161
180
|
export async function loadRetryQueue() {
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
181
|
+
const url = "/api/retry-queue";
|
|
182
|
+
if (_cacheFresh(url, "retry-queue")) return;
|
|
183
|
+
const res = await apiFetch(url, { _silent: true }).catch(() => ({ ok: false, items: [], count: 0, stats: null }));
|
|
184
|
+
retryQueueData.value = normalizeRetryQueuePayload(res);
|
|
185
|
+
_cacheSet(url, retryQueueData.value);
|
|
186
|
+
_markFresh("retry-queue");
|
|
166
187
|
retryQueueLoaded.value = true;
|
|
167
188
|
}
|
|
168
189
|
|
|
@@ -353,61 +374,18 @@ export function shouldShowToast(toast) {
|
|
|
353
374
|
}
|
|
354
375
|
|
|
355
376
|
/* ═══════════════════════════════════════════════════════════════
|
|
356
|
-
*
|
|
377
|
+
* LEGACY STORED DEFAULTS MIGRATION HOOK
|
|
357
378
|
* ═══════════════════════════════════════════════════════════════ */
|
|
358
379
|
|
|
359
380
|
let _defaultsApplied = false;
|
|
360
381
|
|
|
361
382
|
/**
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
* Only runs once per app lifecycle (not on tab switches).
|
|
383
|
+
* Reserved for one-time client preference migrations.
|
|
384
|
+
* Executor runtime defaults now live only in Server Config.
|
|
365
385
|
*/
|
|
366
386
|
export async function applyStoredDefaults() {
|
|
367
387
|
if (_defaultsApplied) return;
|
|
368
388
|
_defaultsApplied = true;
|
|
369
|
-
|
|
370
|
-
const [maxP, sdk, region] = await Promise.all([
|
|
371
|
-
_cloudGet("defaultMaxParallel"),
|
|
372
|
-
_cloudGet("defaultSdk"),
|
|
373
|
-
_cloudGet("defaultRegion"),
|
|
374
|
-
]);
|
|
375
|
-
|
|
376
|
-
const promises = [];
|
|
377
|
-
|
|
378
|
-
if (maxP != null) {
|
|
379
|
-
const current = executorData.value;
|
|
380
|
-
const currentMax =
|
|
381
|
-
current?.data?.maxParallel ??
|
|
382
|
-
current?.maxParallel ??
|
|
383
|
-
null;
|
|
384
|
-
const isPaused = Boolean(current?.paused || current?.data?.paused);
|
|
385
|
-
if (!isPaused && currentMax !== maxP) {
|
|
386
|
-
promises.push(
|
|
387
|
-
apiFetch("/api/executor/maxparallel", {
|
|
388
|
-
method: "POST",
|
|
389
|
-
body: JSON.stringify({ maxParallel: maxP }),
|
|
390
|
-
_silent: true,
|
|
391
|
-
}).catch(() => {}),
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const settingsUpdates = {};
|
|
397
|
-
if (sdk && sdk !== "auto") settingsUpdates.INTERNAL_EXECUTOR_SDK = sdk;
|
|
398
|
-
if (region && region !== "auto") settingsUpdates.EXECUTOR_REGIONS = region;
|
|
399
|
-
|
|
400
|
-
if (Object.keys(settingsUpdates).length) {
|
|
401
|
-
promises.push(
|
|
402
|
-
apiFetch("/api/settings/update", {
|
|
403
|
-
method: "POST",
|
|
404
|
-
body: JSON.stringify({ changes: settingsUpdates }),
|
|
405
|
-
_silent: true,
|
|
406
|
-
}).catch(() => {}),
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (promises.length) await Promise.all(promises);
|
|
411
389
|
}
|
|
412
390
|
|
|
413
391
|
/* ═══════════════════════════════════════════════════════════════
|
|
@@ -667,9 +645,10 @@ export async function loadInfra() {
|
|
|
667
645
|
}
|
|
668
646
|
|
|
669
647
|
/** Load system logs → logsData */
|
|
670
|
-
export async function loadLogs() {
|
|
648
|
+
export async function loadLogs(options = {}) {
|
|
671
649
|
const url = `/api/logs?lines=${logsLines.value}`;
|
|
672
|
-
|
|
650
|
+
const force = Boolean(options?.force);
|
|
651
|
+
if (!force && _cacheFresh(url, "logs")) return;
|
|
673
652
|
const res = await apiFetch(url, { _silent: true }).catch(() => ({ data: null }));
|
|
674
653
|
logsData.value = res.data ?? res ?? null;
|
|
675
654
|
_cacheSet(url, logsData.value);
|
|
@@ -709,7 +688,7 @@ export async function loadAgentLogFileList() {
|
|
|
709
688
|
}
|
|
710
689
|
|
|
711
690
|
/** Load tail of the currently selected agent log → agentLogTail */
|
|
712
|
-
export async function loadAgentLogTailData() {
|
|
691
|
+
export async function loadAgentLogTailData(options = {}) {
|
|
713
692
|
if (!agentLogFile.value) {
|
|
714
693
|
agentLogTail.value = null;
|
|
715
694
|
return;
|
|
@@ -718,10 +697,20 @@ export async function loadAgentLogTailData() {
|
|
|
718
697
|
file: agentLogFile.value,
|
|
719
698
|
lines: String(agentLogLines.value),
|
|
720
699
|
});
|
|
721
|
-
const
|
|
700
|
+
const url = `/api/agent-logs/tail?${params}`;
|
|
701
|
+
if (!options?.force && _cacheFresh(url, "logs")) {
|
|
702
|
+
const cached = _cacheGet(url);
|
|
703
|
+
if (cached) {
|
|
704
|
+
agentLogTail.value = cached.data;
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const res = await apiFetch(url, {
|
|
722
709
|
_silent: true,
|
|
723
710
|
}).catch(() => ({ data: null }));
|
|
724
711
|
agentLogTail.value = res.data ?? res ?? null;
|
|
712
|
+
_cacheSet(url, agentLogTail.value);
|
|
713
|
+
_markFresh("logs");
|
|
725
714
|
}
|
|
726
715
|
|
|
727
716
|
/**
|
|
@@ -884,7 +873,7 @@ export async function loadBenchmarks(providerId = "") {
|
|
|
884
873
|
|
|
885
874
|
const TAB_LOADERS = {
|
|
886
875
|
dashboard: () =>
|
|
887
|
-
Promise.all([loadStatus(), loadExecutor(), loadProjectSummary()]),
|
|
876
|
+
Promise.all([loadStatus(), loadExecutor(), loadProjectSummary(), loadRetryQueue()]),
|
|
888
877
|
tasks: () => loadTasks(),
|
|
889
878
|
benchmarks: () => loadBenchmarks(),
|
|
890
879
|
agents: () => Promise.all([loadAgents(), loadExecutor(), import("../components/session-list.js").then((m) => m.loadSessions()).catch(() => {})]),
|
|
@@ -905,6 +894,7 @@ const TAB_LOADERS = {
|
|
|
905
894
|
loadTelemetryExecutors(),
|
|
906
895
|
loadTelemetryAlerts(),
|
|
907
896
|
loadUsageAnalytics(30),
|
|
897
|
+
loadRetryQueue(),
|
|
908
898
|
]),
|
|
909
899
|
settings: () => Promise.all([loadStatus(), loadConfig()]),
|
|
910
900
|
};
|
|
@@ -990,20 +980,27 @@ export function scheduleRefresh(ms = 5000) {
|
|
|
990
980
|
/* ─── WebSocket invalidation listener ─── */
|
|
991
981
|
|
|
992
982
|
const WS_CHANNEL_MAP = {
|
|
993
|
-
dashboard: ["overview", "executor", "tasks", "agents"],
|
|
983
|
+
dashboard: ["overview", "executor", "tasks", "agents", "retry-queue"],
|
|
994
984
|
tasks: ["tasks"],
|
|
995
985
|
benchmarks: ["benchmarks", "tasks", "executor", "workflows", "workspaces", "library"],
|
|
996
986
|
agents: ["agents", "executor"],
|
|
997
987
|
infra: ["worktrees", "workspaces", "presence"],
|
|
998
988
|
control: ["executor", "overview"],
|
|
999
989
|
logs: ["*"],
|
|
1000
|
-
telemetry: ["*"],
|
|
990
|
+
telemetry: ["*", "retry-queue"],
|
|
1001
991
|
settings: ["overview"],
|
|
1002
992
|
};
|
|
1003
993
|
|
|
1004
994
|
/** Start listening for WS invalidation messages and auto-refreshing. */
|
|
1005
995
|
export function initWsInvalidationListener() {
|
|
1006
996
|
onWsMessage((msg) => {
|
|
997
|
+
if (msg?.type === "retry-queue-updated") {
|
|
998
|
+
retryQueueData.value = normalizeRetryQueuePayload(msg?.payload);
|
|
999
|
+
_cacheSet("/api/retry-queue", retryQueueData.value);
|
|
1000
|
+
_markFresh("retry-queue");
|
|
1001
|
+
retryQueueLoaded.value = true;
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1007
1004
|
if (msg?.type !== "invalidate") return;
|
|
1008
1005
|
const channels = Array.isArray(msg.channels) ? msg.channels : [];
|
|
1009
1006
|
// Clear cache for invalidated channels so next fetch is fresh
|