bosun 0.35.1 → 0.35.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/monitor.mjs +51 -66
- package/package.json +1 -1
- package/telegram-bot.mjs +27 -1
package/monitor.mjs
CHANGED
|
@@ -724,7 +724,7 @@ async function pollAgentAlerts() {
|
|
|
724
724
|
telegramChatId &&
|
|
725
725
|
process.env.AGENT_ALERTS_NOTIFY === "true"
|
|
726
726
|
) {
|
|
727
|
-
runDetached("agent-alerts:
|
|
727
|
+
runDetached("agent-alerts:notify", () =>
|
|
728
728
|
sendTelegramMessage(formatAgentAlert(alert), {
|
|
729
729
|
dedupKey: `agent-alert:${alert.type || "alert"}:${alert.attempt_id || "unknown"}`,
|
|
730
730
|
}),
|
|
@@ -780,10 +780,10 @@ function startAgentAlertTailer() {
|
|
|
780
780
|
if (agentAlertsTimer) return;
|
|
781
781
|
loadAgentAlertsState();
|
|
782
782
|
agentAlertsTimer = setInterval(() => {
|
|
783
|
-
runDetached("agent-alerts:poll", pollAgentAlerts);
|
|
783
|
+
runDetached("agent-alerts:poll-interval", pollAgentAlerts);
|
|
784
784
|
}, AGENT_ALERT_POLL_MS);
|
|
785
785
|
agentAlertsTimer.unref?.();
|
|
786
|
-
runDetached("agent-alerts:poll", pollAgentAlerts);
|
|
786
|
+
runDetached("agent-alerts:poll-startup", pollAgentAlerts);
|
|
787
787
|
}
|
|
788
788
|
|
|
789
789
|
function stopAgentAlertTailer() {
|
|
@@ -2681,9 +2681,7 @@ async function handleMonitorFailure(reason, err) {
|
|
|
2681
2681
|
// Ensure we retry after safe-mode window if still running.
|
|
2682
2682
|
if (!shuttingDown) {
|
|
2683
2683
|
setTimeout(() => {
|
|
2684
|
-
if (!shuttingDown)
|
|
2685
|
-
runDetached("start-process:hard-cap-retry", startProcess);
|
|
2686
|
-
}
|
|
2684
|
+
if (!shuttingDown) void startProcess();
|
|
2687
2685
|
}, pauseMs + 1000);
|
|
2688
2686
|
}
|
|
2689
2687
|
return;
|
|
@@ -2790,19 +2788,25 @@ function runGuarded(reason, fn) {
|
|
|
2790
2788
|
reportGuardedFailure(reason, err);
|
|
2791
2789
|
}
|
|
2792
2790
|
}
|
|
2791
|
+
|
|
2793
2792
|
function runDetached(label, promiseOrFn) {
|
|
2794
|
-
|
|
2795
|
-
const message = formatMonitorError(err);
|
|
2796
|
-
console.warn(`[monitor] detached ${label} failed: ${message}`);
|
|
2797
|
-
};
|
|
2793
|
+
if (shuttingDown) return;
|
|
2798
2794
|
try {
|
|
2799
|
-
const
|
|
2795
|
+
const pending =
|
|
2800
2796
|
typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
|
|
2801
|
-
if (
|
|
2802
|
-
|
|
2797
|
+
if (pending && typeof pending.then === "function") {
|
|
2798
|
+
pending.catch((err) => {
|
|
2799
|
+
const error = err instanceof Error ? err : new Error(formatMonitorError(err));
|
|
2800
|
+
console.warn(
|
|
2801
|
+
`[monitor] detached task failed (${label}): ${error.stack || error.message}`,
|
|
2802
|
+
);
|
|
2803
|
+
});
|
|
2803
2804
|
}
|
|
2804
2805
|
} catch (err) {
|
|
2805
|
-
|
|
2806
|
+
const error = err instanceof Error ? err : new Error(formatMonitorError(err));
|
|
2807
|
+
console.warn(
|
|
2808
|
+
`[monitor] detached task failed (${label}): ${error.stack || error.message}`,
|
|
2809
|
+
);
|
|
2806
2810
|
}
|
|
2807
2811
|
}
|
|
2808
2812
|
|
|
@@ -3185,19 +3189,17 @@ function notifyVkError(line) {
|
|
|
3185
3189
|
]
|
|
3186
3190
|
.filter(Boolean)
|
|
3187
3191
|
.join("\n");
|
|
3188
|
-
runDetached("vk-error:
|
|
3192
|
+
runDetached("vk-error:notify", () =>
|
|
3189
3193
|
sendTelegramMessage(message, { parseMode: "HTML" }),
|
|
3190
3194
|
);
|
|
3191
|
-
runDetached("vk-recovery
|
|
3195
|
+
runDetached("vk-error:trigger-recovery", () => triggerVibeKanbanRecovery(line));
|
|
3192
3196
|
}
|
|
3193
3197
|
|
|
3194
3198
|
function notifyCodexTrigger(context) {
|
|
3195
3199
|
if (!telegramToken || !telegramChatId) {
|
|
3196
3200
|
return;
|
|
3197
3201
|
}
|
|
3198
|
-
|
|
3199
|
-
sendTelegramMessage(`Codex triggered: ${context}`),
|
|
3200
|
-
);
|
|
3202
|
+
void sendTelegramMessage(`Codex triggered: ${context}`);
|
|
3201
3203
|
}
|
|
3202
3204
|
|
|
3203
3205
|
async function runCodexRecovery(reason) {
|
|
@@ -3431,10 +3433,7 @@ function scheduleVibeKanbanRestart() {
|
|
|
3431
3433
|
console.log(
|
|
3432
3434
|
`[monitor] restarting vibe-kanban in ${delay}ms (attempt ${vkRestartCount}/${vkMaxRestarts})`,
|
|
3433
3435
|
);
|
|
3434
|
-
setTimeout(
|
|
3435
|
-
() => runDetached("vk-restart:scheduled", startVibeKanbanProcess),
|
|
3436
|
-
delay,
|
|
3437
|
-
);
|
|
3436
|
+
setTimeout(() => void startVibeKanbanProcess(), delay);
|
|
3438
3437
|
}
|
|
3439
3438
|
|
|
3440
3439
|
async function canConnectTcp(host, port, timeoutMs = 1200) {
|
|
@@ -3532,7 +3531,7 @@ function restartVibeKanbanProcess() {
|
|
|
3532
3531
|
/* best effort */
|
|
3533
3532
|
}
|
|
3534
3533
|
} else {
|
|
3535
|
-
|
|
3534
|
+
void startVibeKanbanProcess();
|
|
3536
3535
|
}
|
|
3537
3536
|
}
|
|
3538
3537
|
|
|
@@ -3747,9 +3746,7 @@ function ensureVkLogStream() {
|
|
|
3747
3746
|
}
|
|
3748
3747
|
|
|
3749
3748
|
// Discover any active sessions immediately and keep polling for new sessions
|
|
3750
|
-
|
|
3751
|
-
refreshVkSessionStreams("startup"),
|
|
3752
|
-
);
|
|
3749
|
+
void refreshVkSessionStreams("startup");
|
|
3753
3750
|
ensureVkSessionDiscoveryLoop();
|
|
3754
3751
|
}
|
|
3755
3752
|
|
|
@@ -3757,9 +3754,7 @@ function ensureVkSessionDiscoveryLoop() {
|
|
|
3757
3754
|
if (vkSessionDiscoveryTimer) return;
|
|
3758
3755
|
if (!Number.isFinite(vkEnsureIntervalMs) || vkEnsureIntervalMs <= 0) return;
|
|
3759
3756
|
vkSessionDiscoveryTimer = setInterval(() => {
|
|
3760
|
-
|
|
3761
|
-
refreshVkSessionStreams("periodic"),
|
|
3762
|
-
);
|
|
3757
|
+
void refreshVkSessionStreams("periodic");
|
|
3763
3758
|
}, vkEnsureIntervalMs);
|
|
3764
3759
|
}
|
|
3765
3760
|
|
|
@@ -3941,9 +3936,7 @@ async function triggerVibeKanbanRecovery(reason) {
|
|
|
3941
3936
|
const notice = codexEnabled
|
|
3942
3937
|
? `Codex recovery triggered: vibe-kanban unreachable. Attempting restart. (${link})`
|
|
3943
3938
|
: `Vibe-kanban recovery triggered (Codex disabled). Attempting restart. (${link})`;
|
|
3944
|
-
|
|
3945
|
-
sendTelegramMessage(notice, { parseMode: "HTML" }),
|
|
3946
|
-
);
|
|
3939
|
+
void sendTelegramMessage(notice, { parseMode: "HTML" });
|
|
3947
3940
|
}
|
|
3948
3941
|
await runCodexRecovery(reason || "vibe-kanban unreachable");
|
|
3949
3942
|
restartVibeKanbanProcess();
|
|
@@ -4053,7 +4046,7 @@ async function fetchVk(path, opts = {}) {
|
|
|
4053
4046
|
if (shouldLogVkWarning("network-error")) {
|
|
4054
4047
|
console.warn(`[monitor] fetchVk ${method} ${path} error: ${msg}`);
|
|
4055
4048
|
}
|
|
4056
|
-
runDetached("
|
|
4049
|
+
runDetached("fetchVk:network-recovery", () =>
|
|
4057
4050
|
triggerVibeKanbanRecovery(
|
|
4058
4051
|
`fetchVk ${method} ${path} network error: ${msg}`,
|
|
4059
4052
|
),
|
|
@@ -4074,7 +4067,7 @@ async function fetchVk(path, opts = {}) {
|
|
|
4074
4067
|
`[monitor] fetchVk ${method} ${path} error: invalid response object (res=${!!res}, res.ok=${res?.ok})`,
|
|
4075
4068
|
);
|
|
4076
4069
|
}
|
|
4077
|
-
runDetached("
|
|
4070
|
+
runDetached("fetchVk:invalid-response-recovery", () =>
|
|
4078
4071
|
triggerVibeKanbanRecovery(
|
|
4079
4072
|
`fetchVk ${method} ${path} invalid response object`,
|
|
4080
4073
|
),
|
|
@@ -4091,7 +4084,7 @@ async function fetchVk(path, opts = {}) {
|
|
|
4091
4084
|
);
|
|
4092
4085
|
}
|
|
4093
4086
|
if (res.status >= 500) {
|
|
4094
|
-
runDetached("
|
|
4087
|
+
runDetached("fetchVk:http-5xx-recovery", () =>
|
|
4095
4088
|
triggerVibeKanbanRecovery(
|
|
4096
4089
|
`fetchVk ${method} ${path} HTTP ${res.status}`,
|
|
4097
4090
|
),
|
|
@@ -4132,7 +4125,7 @@ async function fetchVk(path, opts = {}) {
|
|
|
4132
4125
|
);
|
|
4133
4126
|
}
|
|
4134
4127
|
}
|
|
4135
|
-
runDetached("
|
|
4128
|
+
runDetached("fetchVk:non-json-recovery", () =>
|
|
4136
4129
|
triggerVibeKanbanRecovery(
|
|
4137
4130
|
`fetchVk ${method} ${path} non-JSON response`,
|
|
4138
4131
|
),
|
|
@@ -8094,12 +8087,10 @@ async function runMergeStrategyAnalysis(ctx, opts = {}) {
|
|
|
8094
8087
|
// Re-run analysis after the wait period
|
|
8095
8088
|
setTimeout(
|
|
8096
8089
|
() => {
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
}),
|
|
8102
|
-
);
|
|
8090
|
+
void runMergeStrategyAnalysis({
|
|
8091
|
+
...ctx,
|
|
8092
|
+
ciStatus: "re-check",
|
|
8093
|
+
});
|
|
8103
8094
|
},
|
|
8104
8095
|
(execResult.waitSeconds || 300) * 1000,
|
|
8105
8096
|
);
|
|
@@ -8406,12 +8397,10 @@ async function actOnAssessment(ctx, decision) {
|
|
|
8406
8397
|
const waitSec = decision.waitSeconds || 300;
|
|
8407
8398
|
console.log(`[${tag}] → wait ${waitSec}s`);
|
|
8408
8399
|
setTimeout(() => {
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
}),
|
|
8414
|
-
);
|
|
8400
|
+
void runTaskAssessment({
|
|
8401
|
+
...ctx,
|
|
8402
|
+
trigger: "reassessment",
|
|
8403
|
+
});
|
|
8415
8404
|
}, waitSec * 1000);
|
|
8416
8405
|
break;
|
|
8417
8406
|
}
|
|
@@ -10379,7 +10368,7 @@ globalThis.__bosunNotifyAnomaly = (anomaly) => {
|
|
|
10379
10368
|
|
|
10380
10369
|
function enqueueTelegramCommand(handler) {
|
|
10381
10370
|
telegramCommandQueue.push(handler);
|
|
10382
|
-
|
|
10371
|
+
runDetached("telegram-commands:drain", drainTelegramCommandQueue);
|
|
10383
10372
|
}
|
|
10384
10373
|
|
|
10385
10374
|
function drainTelegramCommandQueue() {
|
|
@@ -10851,7 +10840,6 @@ async function startTelegramNotifier() {
|
|
|
10851
10840
|
await checkStatusMilestones();
|
|
10852
10841
|
};
|
|
10853
10842
|
|
|
10854
|
-
|
|
10855
10843
|
// Suppress "Notifier started" message on rapid restarts (e.g. code-change restarts).
|
|
10856
10844
|
// If the last start was <60s ago, skip the notification — just log locally.
|
|
10857
10845
|
const lastStartPath = resolve(
|
|
@@ -10876,16 +10864,18 @@ async function startTelegramNotifier() {
|
|
|
10876
10864
|
`[monitor] notifier restarted (suppressed telegram notification — rapid restart)`,
|
|
10877
10865
|
);
|
|
10878
10866
|
} else {
|
|
10879
|
-
runDetached("telegram-notifier:startup", () =>
|
|
10867
|
+
runDetached("telegram-notifier:startup-message", () =>
|
|
10880
10868
|
sendTelegramMessage(`${projectName} Orchestrator Notifier started.`),
|
|
10881
10869
|
);
|
|
10882
10870
|
}
|
|
10883
|
-
telegramNotifierTimeout = setTimeout(
|
|
10884
|
-
runDetached("telegram-notifier:
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10871
|
+
telegramNotifierTimeout = setTimeout(
|
|
10872
|
+
() => runDetached("telegram-notifier:timeout-update", sendUpdate),
|
|
10873
|
+
intervalMs,
|
|
10874
|
+
);
|
|
10875
|
+
telegramNotifierInterval = setInterval(
|
|
10876
|
+
() => runDetached("telegram-notifier:interval-update", sendUpdate),
|
|
10877
|
+
intervalMs,
|
|
10878
|
+
);
|
|
10889
10879
|
}
|
|
10890
10880
|
|
|
10891
10881
|
async function checkStatusMilestones() {
|
|
@@ -13921,9 +13911,7 @@ async function startProcess() {
|
|
|
13921
13911
|
if (!shuttingDown) {
|
|
13922
13912
|
const retryMs = Math.max(5_000, restartDelayMs || 0);
|
|
13923
13913
|
safeSetTimeout("startProcess-retry", () => {
|
|
13924
|
-
if (!shuttingDown)
|
|
13925
|
-
return startProcess();
|
|
13926
|
-
}
|
|
13914
|
+
if (!shuttingDown) void startProcess();
|
|
13927
13915
|
}, retryMs);
|
|
13928
13916
|
}
|
|
13929
13917
|
}
|
|
@@ -16057,10 +16045,7 @@ if (isExecutorDisabled()) {
|
|
|
16057
16045
|
if (projectId) {
|
|
16058
16046
|
syncEngine = createSyncEngine({
|
|
16059
16047
|
projectId,
|
|
16060
|
-
syncIntervalMs:
|
|
16061
|
-
60_000,
|
|
16062
|
-
Number(process.env.KANBAN_SYNC_INTERVAL_MS) || 5 * 60 * 1000,
|
|
16063
|
-
), // default 5 min (env: KANBAN_SYNC_INTERVAL_MS)
|
|
16048
|
+
syncIntervalMs: 60_000, // 1 minute
|
|
16064
16049
|
syncPolicy: kanbanConfig?.syncPolicy || "internal-primary",
|
|
16065
16050
|
sendTelegram:
|
|
16066
16051
|
telegramToken && telegramChatId
|
|
@@ -16080,7 +16065,7 @@ if (isExecutorDisabled()) {
|
|
|
16080
16065
|
});
|
|
16081
16066
|
syncEngine.start();
|
|
16082
16067
|
console.log(
|
|
16083
|
-
`[monitor] sync engine started (interval:
|
|
16068
|
+
`[monitor] sync engine started (interval: 60s, backend=${activeKanbanBackend}, policy=${kanbanConfig?.syncPolicy || "internal-primary"}, project=${projectId})`,
|
|
16084
16069
|
);
|
|
16085
16070
|
} else {
|
|
16086
16071
|
console.log(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.2",
|
|
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/telegram-bot.mjs
CHANGED
|
@@ -935,7 +935,30 @@ function getBrowserUiUrl() {
|
|
|
935
935
|
return appendTokenToUrl(base, token) || base;
|
|
936
936
|
}
|
|
937
937
|
|
|
938
|
-
function
|
|
938
|
+
function isTelegramInlineButtonUrlAllowed(inputUrl) {
|
|
939
|
+
try {
|
|
940
|
+
const parsed = new URL(String(inputUrl || "").trim());
|
|
941
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
const host = String(parsed.hostname || "").toLowerCase();
|
|
945
|
+
if (!host) return false;
|
|
946
|
+
// Telegram rejects localhost/loopback URLs for inline keyboard URL buttons.
|
|
947
|
+
if (
|
|
948
|
+
host === "localhost" ||
|
|
949
|
+
host === "127.0.0.1" ||
|
|
950
|
+
host === "::1" ||
|
|
951
|
+
host === "[::1]"
|
|
952
|
+
) {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
return true;
|
|
956
|
+
} catch {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function getBrowserUiUrlOptions({ forTelegramButtons = true } = {}) {
|
|
939
962
|
const base = String(telegramUiUrl || "").trim();
|
|
940
963
|
if (!base) return [];
|
|
941
964
|
|
|
@@ -949,6 +972,9 @@ function getBrowserUiUrlOptions() {
|
|
|
949
972
|
if (!url) return;
|
|
950
973
|
if (seen.has(url)) return;
|
|
951
974
|
seen.add(url);
|
|
975
|
+
if (forTelegramButtons && !isTelegramInlineButtonUrlAllowed(url)) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
952
978
|
options.push({ label, url });
|
|
953
979
|
};
|
|
954
980
|
|