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
package/server/ui-server.mjs
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
execWithRetry,
|
|
51
51
|
invalidateThread,
|
|
52
52
|
} from "../agent/agent-pool.mjs";
|
|
53
|
+
import { withTaskLifetimeTotals } from "../infra/runtime-accumulator.mjs";
|
|
53
54
|
import { resolveAgentPrompts } from "../agent/agent-prompts.mjs";
|
|
54
55
|
import {
|
|
55
56
|
listActiveWorktrees,
|
|
@@ -140,6 +141,12 @@ import {
|
|
|
140
141
|
addSessionEventListener,
|
|
141
142
|
} from "../infra/session-tracker.mjs";
|
|
142
143
|
import { ensureTestRuntimeSandbox } from "../infra/test-runtime.mjs";
|
|
144
|
+
import {
|
|
145
|
+
addSessionAccumulationListener,
|
|
146
|
+
getCompletedSessions,
|
|
147
|
+
getRuntimeStats,
|
|
148
|
+
getTaskLifetimeTotals,
|
|
149
|
+
} from "../infra/runtime-accumulator.mjs";
|
|
143
150
|
import {
|
|
144
151
|
collectDiffStats,
|
|
145
152
|
getCompactDiffSummary,
|
|
@@ -3462,6 +3469,7 @@ const DEFAULT_AUTO_OPEN_COOLDOWN_MS = 12 * 60 * 60 * 1000; // 12h
|
|
|
3462
3469
|
const DEFAULT_SESSION_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
3463
3470
|
const wsClients = new Set();
|
|
3464
3471
|
let sessionListenerAttached = false;
|
|
3472
|
+
let sessionAccumulatorListenerAttached = false;
|
|
3465
3473
|
/** @type {ReturnType<typeof setInterval>|null} */
|
|
3466
3474
|
let wsHeartbeatTimer = null;
|
|
3467
3475
|
const WORKFLOW_WS_BATCH_MS = 80;
|
|
@@ -7128,6 +7136,20 @@ async function reconcileTaskAfterDispatchAttempt({
|
|
|
7128
7136
|
return persistTaskStatusForExecution(adapter, taskId, targetStatus, source);
|
|
7129
7137
|
}
|
|
7130
7138
|
|
|
7139
|
+
function enrichTaskLifetimeTotals(task) {
|
|
7140
|
+
if (!task || typeof task !== "object") return task;
|
|
7141
|
+
const taskId = String(task?.id || task?.taskId || "").trim();
|
|
7142
|
+
const lifetimeTotals = taskId ? getTaskLifetimeTotals(taskId) : null;
|
|
7143
|
+
return {
|
|
7144
|
+
...task,
|
|
7145
|
+
lifetimeTotals,
|
|
7146
|
+
meta: {
|
|
7147
|
+
...(task.meta || {}),
|
|
7148
|
+
lifetimeTotals,
|
|
7149
|
+
},
|
|
7150
|
+
};
|
|
7151
|
+
}
|
|
7152
|
+
|
|
7131
7153
|
function buildTaskRuntimeSnapshot(task) {
|
|
7132
7154
|
const runtimeExecutor = uiDeps.getInternalExecutor?.() || null;
|
|
7133
7155
|
const status = runtimeExecutor?.getStatus?.() || {};
|
|
@@ -7146,6 +7168,7 @@ function buildTaskRuntimeSnapshot(task) {
|
|
|
7146
7168
|
taskId,
|
|
7147
7169
|
taskStatus: task?.status || null,
|
|
7148
7170
|
statusLabel: "Live execution",
|
|
7171
|
+
lifetimeTotals: task?.lifetimeTotals || task?.meta?.lifetimeTotals || null,
|
|
7149
7172
|
slot: {
|
|
7150
7173
|
taskId,
|
|
7151
7174
|
branch: slot?.branch || slot?.branchName || null,
|
|
@@ -7169,6 +7192,7 @@ function buildTaskRuntimeSnapshot(task) {
|
|
|
7169
7192
|
taskStatus: task?.status || null,
|
|
7170
7193
|
statusLabel: "Queued for execution",
|
|
7171
7194
|
reason: "no_free_slots",
|
|
7195
|
+
lifetimeTotals: task?.lifetimeTotals || task?.meta?.lifetimeTotals || null,
|
|
7172
7196
|
};
|
|
7173
7197
|
}
|
|
7174
7198
|
if (normalizedStatus === "inprogress") {
|
|
@@ -7179,6 +7203,7 @@ function buildTaskRuntimeSnapshot(task) {
|
|
|
7179
7203
|
taskStatus: task?.status || null,
|
|
7180
7204
|
statusLabel: "No live execution detected",
|
|
7181
7205
|
reason: "no_active_executor_slot",
|
|
7206
|
+
lifetimeTotals: task?.lifetimeTotals || task?.meta?.lifetimeTotals || null,
|
|
7182
7207
|
};
|
|
7183
7208
|
}
|
|
7184
7209
|
if (normalizedStatus === "inreview") {
|
|
@@ -7188,6 +7213,7 @@ function buildTaskRuntimeSnapshot(task) {
|
|
|
7188
7213
|
taskId,
|
|
7189
7214
|
taskStatus: task?.status || null,
|
|
7190
7215
|
statusLabel: "Awaiting review",
|
|
7216
|
+
lifetimeTotals: task?.lifetimeTotals || task?.meta?.lifetimeTotals || null,
|
|
7191
7217
|
};
|
|
7192
7218
|
}
|
|
7193
7219
|
return {
|
|
@@ -7196,17 +7222,19 @@ function buildTaskRuntimeSnapshot(task) {
|
|
|
7196
7222
|
taskId,
|
|
7197
7223
|
taskStatus: task?.status || null,
|
|
7198
7224
|
statusLabel: "No active execution",
|
|
7225
|
+
lifetimeTotals: task?.lifetimeTotals || task?.meta?.lifetimeTotals || null,
|
|
7199
7226
|
};
|
|
7200
7227
|
}
|
|
7201
7228
|
|
|
7202
7229
|
function withTaskRuntimeSnapshot(task) {
|
|
7203
7230
|
if (!task || typeof task !== "object") return task;
|
|
7204
|
-
const
|
|
7231
|
+
const withLifetimeTotals = enrichTaskLifetimeTotals(task);
|
|
7232
|
+
const runtimeSnapshot = buildTaskRuntimeSnapshot(withLifetimeTotals);
|
|
7205
7233
|
return {
|
|
7206
|
-
...
|
|
7234
|
+
...withLifetimeTotals,
|
|
7207
7235
|
runtimeSnapshot,
|
|
7208
7236
|
meta: {
|
|
7209
|
-
...(
|
|
7237
|
+
...(withLifetimeTotals.meta || {}),
|
|
7210
7238
|
runtimeSnapshot,
|
|
7211
7239
|
},
|
|
7212
7240
|
};
|
|
@@ -8040,10 +8068,12 @@ async function collectUiStats() {
|
|
|
8040
8068
|
} catch { /* best effort */ }
|
|
8041
8069
|
}
|
|
8042
8070
|
|
|
8071
|
+
const runtimeStats = getRuntimeStats();
|
|
8072
|
+
|
|
8043
8073
|
return {
|
|
8044
8074
|
uptimeMs: process.uptime() * 1000,
|
|
8045
|
-
runtimeMs:
|
|
8046
|
-
totalCostUsd:
|
|
8075
|
+
runtimeMs: runtimeStats.runtimeMs || 0,
|
|
8076
|
+
totalCostUsd: runtimeStats.totalCostUsd || 0,
|
|
8047
8077
|
totalSessions: sessionStats.total,
|
|
8048
8078
|
activeSessions: sessionStats.active,
|
|
8049
8079
|
completedSessions: sessionStats.completed,
|
|
@@ -8055,7 +8085,24 @@ async function collectUiStats() {
|
|
|
8055
8085
|
queuedTasks: taskStats.queued,
|
|
8056
8086
|
activeSlots: orchestratorStatus?.active_slots || "0/0",
|
|
8057
8087
|
executorMode: orchestratorStatus?.executor_mode || "unknown",
|
|
8058
|
-
retryQueue:
|
|
8088
|
+
retryQueue: (() => {
|
|
8089
|
+
const bus = _resolveEventBus();
|
|
8090
|
+
if (bus && typeof bus.getRetryQueue === "function") {
|
|
8091
|
+
try {
|
|
8092
|
+
const snapshot = bus.getRetryQueue();
|
|
8093
|
+
if (snapshot && typeof snapshot === "object") return snapshot;
|
|
8094
|
+
} catch {
|
|
8095
|
+
/* best effort */
|
|
8096
|
+
}
|
|
8097
|
+
}
|
|
8098
|
+
return globalThis.__bosun_setRetryQueueData
|
|
8099
|
+
? _retryQueue
|
|
8100
|
+
: {
|
|
8101
|
+
count: 0,
|
|
8102
|
+
items: [],
|
|
8103
|
+
stats: { totalRetriesToday: 0, peakRetryDepth: 0, exhaustedTaskIds: [] },
|
|
8104
|
+
};
|
|
8105
|
+
})(),
|
|
8059
8106
|
workflows: {
|
|
8060
8107
|
active: globalThis.__bosun_activeWorkflows || [],
|
|
8061
8108
|
total: globalThis.__bosun_totalWorkflows || 0,
|
|
@@ -8111,20 +8158,30 @@ async function listDirFilesWithMtime(dir, predicate = () => true) {
|
|
|
8111
8158
|
return entries.filter(Boolean);
|
|
8112
8159
|
}
|
|
8113
8160
|
|
|
8114
|
-
|
|
8161
|
+
const SYSTEM_LOG_PRIORITY = Object.freeze({
|
|
8162
|
+
"monitor.log": 300,
|
|
8163
|
+
"monitor-error.log": 250,
|
|
8164
|
+
"daemon.log": 200,
|
|
8165
|
+
});
|
|
8166
|
+
|
|
8167
|
+
async function listPreferredSystemLogEntries(limit = 4) {
|
|
8115
8168
|
const rootLogEntries = await listDirFilesWithMtime(
|
|
8116
8169
|
logsDir,
|
|
8117
8170
|
(name) => name.endsWith(".log"),
|
|
8118
8171
|
);
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8172
|
+
return rootLogEntries
|
|
8173
|
+
.sort((a, b) => {
|
|
8174
|
+
const priorityDelta =
|
|
8175
|
+
(SYSTEM_LOG_PRIORITY[b.name] || 0) - (SYSTEM_LOG_PRIORITY[a.name] || 0);
|
|
8176
|
+
if (priorityDelta !== 0) return priorityDelta;
|
|
8177
|
+
return b.mtimeMs - a.mtimeMs;
|
|
8178
|
+
})
|
|
8179
|
+
.slice(0, Math.max(1, limit));
|
|
8180
|
+
}
|
|
8125
8181
|
|
|
8126
|
-
|
|
8127
|
-
|
|
8182
|
+
async function resolvePreferredSystemLogPath() {
|
|
8183
|
+
const preferredEntries = await listPreferredSystemLogEntries(1);
|
|
8184
|
+
return preferredEntries[0]?.path || null;
|
|
8128
8185
|
}
|
|
8129
8186
|
|
|
8130
8187
|
/**
|
|
@@ -9302,10 +9359,70 @@ function withTaskMetadataTopLevel(task) {
|
|
|
9302
9359
|
}
|
|
9303
9360
|
|
|
9304
9361
|
async function getLatestLogTail(lineCount) {
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9362
|
+
return getMergedSystemLogTail(lineCount);
|
|
9363
|
+
}
|
|
9364
|
+
|
|
9365
|
+
function parseSystemLogTimestamp(line) {
|
|
9366
|
+
const match = String(line || "").match(/^\s*(\d{4}-\d{2}-\d{2}T[^\s]+)/);
|
|
9367
|
+
if (!match?.[1]) return Number.NaN;
|
|
9368
|
+
const parsed = Date.parse(match[1]);
|
|
9369
|
+
return Number.isFinite(parsed) ? parsed : Number.NaN;
|
|
9370
|
+
}
|
|
9371
|
+
|
|
9372
|
+
async function getMergedSystemLogTail(
|
|
9373
|
+
lineCount,
|
|
9374
|
+
{
|
|
9375
|
+
fileLimit = 4,
|
|
9376
|
+
maxBytesPerFile = 350_000,
|
|
9377
|
+
} = {},
|
|
9378
|
+
) {
|
|
9379
|
+
const entries = await listPreferredSystemLogEntries(fileLimit);
|
|
9380
|
+
if (!entries.length) {
|
|
9381
|
+
return { file: null, files: [], lines: [], truncated: false };
|
|
9382
|
+
}
|
|
9383
|
+
|
|
9384
|
+
const perFileLineBudget = Math.max(lineCount * 2, 160);
|
|
9385
|
+
const tails = await Promise.all(
|
|
9386
|
+
entries.map((entry) => tailFile(entry.path, perFileLineBudget, maxBytesPerFile).catch(() => null)),
|
|
9387
|
+
);
|
|
9388
|
+
|
|
9389
|
+
const merged = [];
|
|
9390
|
+
let truncated = false;
|
|
9391
|
+
tails.forEach((tail, sourceIndex) => {
|
|
9392
|
+
if (!tail) return;
|
|
9393
|
+
truncated = truncated || tail.truncated === true;
|
|
9394
|
+
const lines = Array.isArray(tail.lines) ? tail.lines : [];
|
|
9395
|
+
lines.forEach((line, lineIndex) => {
|
|
9396
|
+
merged.push({
|
|
9397
|
+
line,
|
|
9398
|
+
timestamp: parseSystemLogTimestamp(line),
|
|
9399
|
+
sourceIndex,
|
|
9400
|
+
lineIndex,
|
|
9401
|
+
});
|
|
9402
|
+
});
|
|
9403
|
+
});
|
|
9404
|
+
|
|
9405
|
+
merged.sort((a, b) => {
|
|
9406
|
+
const aHasTs = Number.isFinite(a.timestamp);
|
|
9407
|
+
const bHasTs = Number.isFinite(b.timestamp);
|
|
9408
|
+
if (aHasTs && bHasTs && a.timestamp !== b.timestamp) {
|
|
9409
|
+
return a.timestamp - b.timestamp;
|
|
9410
|
+
}
|
|
9411
|
+
if (aHasTs !== bHasTs) {
|
|
9412
|
+
return aHasTs ? -1 : 1;
|
|
9413
|
+
}
|
|
9414
|
+
if (a.sourceIndex !== b.sourceIndex) {
|
|
9415
|
+
return a.sourceIndex - b.sourceIndex;
|
|
9416
|
+
}
|
|
9417
|
+
return a.lineIndex - b.lineIndex;
|
|
9418
|
+
});
|
|
9419
|
+
|
|
9420
|
+
return {
|
|
9421
|
+
file: entries[0]?.name || null,
|
|
9422
|
+
files: entries.map((entry) => entry.name),
|
|
9423
|
+
lines: merged.slice(-lineCount).map((entry) => entry.line),
|
|
9424
|
+
truncated,
|
|
9425
|
+
};
|
|
9309
9426
|
}
|
|
9310
9427
|
|
|
9311
9428
|
async function tailFile(filePath, lineCount, maxBytes = 1_000_000) {
|
|
@@ -13494,7 +13611,24 @@ async function handleApi(req, res, url) {
|
|
|
13494
13611
|
const logDir = resolveAgentWorkLogDir();
|
|
13495
13612
|
const metricsPath = resolve(logDir, "agent-metrics.jsonl");
|
|
13496
13613
|
const metrics = await readJsonlTail(metricsPath, 3000);
|
|
13497
|
-
const summary = summarizeTelemetry(metrics, days);
|
|
13614
|
+
const summary = summarizeTelemetry(metrics, days) || {};
|
|
13615
|
+
const runtimeStats = getRuntimeStats();
|
|
13616
|
+
const completedSessions = getCompletedSessions(10_000);
|
|
13617
|
+
const lifetimeTotals = completedSessions.reduce(
|
|
13618
|
+
(acc, session) => {
|
|
13619
|
+
acc.attemptsCount += 1;
|
|
13620
|
+
acc.tokenCount += Number(session?.tokenCount || 0);
|
|
13621
|
+
acc.inputTokens += Number(session?.inputTokens || 0);
|
|
13622
|
+
acc.outputTokens += Number(session?.outputTokens || 0);
|
|
13623
|
+
return acc;
|
|
13624
|
+
},
|
|
13625
|
+
{ attemptsCount: 0, tokenCount: 0, inputTokens: 0, outputTokens: 0 },
|
|
13626
|
+
);
|
|
13627
|
+
summary.runtimeMs = Number(runtimeStats?.runtimeMs || 0);
|
|
13628
|
+
summary.lifetimeTotals = {
|
|
13629
|
+
...lifetimeTotals,
|
|
13630
|
+
durationMs: summary.runtimeMs,
|
|
13631
|
+
};
|
|
13498
13632
|
jsonResponse(res, 200, { ok: true, data: summary });
|
|
13499
13633
|
} catch (err) {
|
|
13500
13634
|
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
@@ -14586,12 +14720,25 @@ async function handleApi(req, res, url) {
|
|
|
14586
14720
|
}
|
|
14587
14721
|
const wfMod = wfCtx.wfMod;
|
|
14588
14722
|
const types = wfMod.listNodeTypes();
|
|
14589
|
-
jsonResponse(res, 200, { ok: true, nodeTypes: types.map(nt =>
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14594
|
-
|
|
14723
|
+
jsonResponse(res, 200, { ok: true, nodeTypes: types.map((nt) => {
|
|
14724
|
+
const rawPorts = nt?.ports && typeof nt.ports === "object" ? nt.ports : {};
|
|
14725
|
+
const ports = {
|
|
14726
|
+
inputs: Array.isArray(rawPorts.inputs) ? rawPorts.inputs : [],
|
|
14727
|
+
outputs: Array.isArray(rawPorts.outputs) ? rawPorts.outputs : [],
|
|
14728
|
+
};
|
|
14729
|
+
return {
|
|
14730
|
+
type: nt.type,
|
|
14731
|
+
category: nt.type.split(".")[0],
|
|
14732
|
+
description: nt.description || "",
|
|
14733
|
+
schema: nt.schema || {},
|
|
14734
|
+
source: nt.source || "builtin",
|
|
14735
|
+
badge: nt.badge || null,
|
|
14736
|
+
isCustom: nt.isCustom === true,
|
|
14737
|
+
filePath: nt.filePath || null,
|
|
14738
|
+
ports,
|
|
14739
|
+
ui: nt?.ui && typeof nt.ui === "object" ? nt.ui : {},
|
|
14740
|
+
};
|
|
14741
|
+
}) });
|
|
14595
14742
|
} catch (err) {
|
|
14596
14743
|
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
14597
14744
|
}
|
|
@@ -15469,6 +15616,14 @@ async function handleApi(req, res, url) {
|
|
|
15469
15616
|
`[telegram-ui] failed to retry task ${taskId}: ${error.message}`,
|
|
15470
15617
|
);
|
|
15471
15618
|
});
|
|
15619
|
+
const bus = _resolveEventBus();
|
|
15620
|
+
if (bus && typeof bus.clearRetryQueueTask === "function") {
|
|
15621
|
+
try {
|
|
15622
|
+
bus.clearRetryQueueTask(taskId, "manual-retry-now");
|
|
15623
|
+
} catch {
|
|
15624
|
+
/* best effort */
|
|
15625
|
+
}
|
|
15626
|
+
}
|
|
15472
15627
|
jsonResponse(res, 200, { ok: true, taskId });
|
|
15473
15628
|
broadcastUiEvent(
|
|
15474
15629
|
["tasks", "overview", "executor", "agents"],
|
|
@@ -15484,14 +15639,45 @@ async function handleApi(req, res, url) {
|
|
|
15484
15639
|
// ── GET /api/retry-queue ───────────────────────────────────────────
|
|
15485
15640
|
if (path === "/api/retry-queue" && req.method === "GET") {
|
|
15486
15641
|
try {
|
|
15487
|
-
const
|
|
15642
|
+
const bus = _resolveEventBus();
|
|
15643
|
+
let retryQueue = null;
|
|
15644
|
+
if (bus && typeof bus.getRetryQueue === "function") {
|
|
15645
|
+
try {
|
|
15646
|
+
const snapshot = bus.getRetryQueue();
|
|
15647
|
+
if (snapshot && typeof snapshot === "object") {
|
|
15648
|
+
retryQueue = snapshot;
|
|
15649
|
+
}
|
|
15650
|
+
} catch {
|
|
15651
|
+
/* best effort */
|
|
15652
|
+
}
|
|
15653
|
+
}
|
|
15654
|
+
if (!retryQueue) {
|
|
15655
|
+
retryQueue = globalThis.__bosun_setRetryQueueData
|
|
15656
|
+
? _retryQueue
|
|
15657
|
+
: {
|
|
15658
|
+
count: 0,
|
|
15659
|
+
items: [],
|
|
15660
|
+
stats: { totalRetriesToday: 0, peakRetryDepth: 0, exhaustedTaskIds: [] },
|
|
15661
|
+
};
|
|
15662
|
+
}
|
|
15488
15663
|
jsonResponse(res, 200, {
|
|
15489
15664
|
ok: true,
|
|
15490
15665
|
count: retryQueue.count || 0,
|
|
15491
15666
|
items: retryQueue.items || [],
|
|
15667
|
+
stats: retryQueue.stats || {
|
|
15668
|
+
totalRetriesToday: 0,
|
|
15669
|
+
peakRetryDepth: 0,
|
|
15670
|
+
exhaustedTaskIds: [],
|
|
15671
|
+
},
|
|
15492
15672
|
});
|
|
15493
15673
|
} catch (err) {
|
|
15494
|
-
jsonResponse(res, 500, {
|
|
15674
|
+
jsonResponse(res, 500, {
|
|
15675
|
+
ok: false,
|
|
15676
|
+
error: err.message,
|
|
15677
|
+
count: 0,
|
|
15678
|
+
items: [],
|
|
15679
|
+
stats: { totalRetriesToday: 0, peakRetryDepth: 0, exhaustedTaskIds: [] },
|
|
15680
|
+
});
|
|
15495
15681
|
}
|
|
15496
15682
|
return;
|
|
15497
15683
|
}
|
|
@@ -18029,6 +18215,17 @@ export async function startTelegramUiServer(options = {}) {
|
|
|
18029
18215
|
broadcastSessionMessage(payload);
|
|
18030
18216
|
});
|
|
18031
18217
|
}
|
|
18218
|
+
if (!sessionAccumulatorListenerAttached) {
|
|
18219
|
+
sessionAccumulatorListenerAttached = true;
|
|
18220
|
+
addSessionAccumulationListener((payload) => {
|
|
18221
|
+
broadcastUiEvent(["tasks", "overview", "telemetry", "sessions"], "invalidate", {
|
|
18222
|
+
reason: "session-accumulated",
|
|
18223
|
+
taskId: payload?.taskId || null,
|
|
18224
|
+
totals: payload?.totals || null,
|
|
18225
|
+
type: payload?.type || "session-accumulated",
|
|
18226
|
+
});
|
|
18227
|
+
});
|
|
18228
|
+
}
|
|
18032
18229
|
|
|
18033
18230
|
// Periodic stats broadcast for TUI
|
|
18034
18231
|
let statsBroadcastInterval = null;
|
|
@@ -18050,7 +18247,29 @@ export async function startTelegramUiServer(options = {}) {
|
|
|
18050
18247
|
// Retry queue tracking
|
|
18051
18248
|
let _retryQueue = { count: 0, items: [] };
|
|
18052
18249
|
function setRetryQueueData(data) {
|
|
18053
|
-
|
|
18250
|
+
const normalized = data && typeof data === "object" ? data : {};
|
|
18251
|
+
_retryQueue = {
|
|
18252
|
+
count: Number(normalized.count || 0),
|
|
18253
|
+
items: Array.isArray(normalized.items) ? normalized.items : [],
|
|
18254
|
+
stats: normalized.stats && typeof normalized.stats === "object"
|
|
18255
|
+
? {
|
|
18256
|
+
totalRetriesToday: Number(normalized.stats.totalRetriesToday || 0),
|
|
18257
|
+
peakRetryDepth: Number(normalized.stats.peakRetryDepth || 0),
|
|
18258
|
+
exhaustedTaskIds: Array.isArray(normalized.stats.exhaustedTaskIds)
|
|
18259
|
+
? normalized.stats.exhaustedTaskIds
|
|
18260
|
+
: [],
|
|
18261
|
+
}
|
|
18262
|
+
: {
|
|
18263
|
+
totalRetriesToday: 0,
|
|
18264
|
+
peakRetryDepth: 0,
|
|
18265
|
+
exhaustedTaskIds: [],
|
|
18266
|
+
},
|
|
18267
|
+
};
|
|
18268
|
+
broadcastUiEvent(["retry-queue", "overview", "telemetry", "tasks"], "retry-queue-updated", _retryQueue);
|
|
18269
|
+
broadcastUiEvent(["overview", "telemetry", "retry-queue"], "invalidate", {
|
|
18270
|
+
reason: "retry-queue-updated",
|
|
18271
|
+
count: _retryQueue.count,
|
|
18272
|
+
});
|
|
18054
18273
|
}
|
|
18055
18274
|
globalThis.__bosun_setRetryQueueData = setRetryQueueData;
|
|
18056
18275
|
|
package/setup.mjs
CHANGED
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
resolveWorkflowTemplateIds,
|
|
61
61
|
normalizeTemplateOverridesById,
|
|
62
62
|
} from "./workflow/workflow-templates.mjs";
|
|
63
|
+
import { discoverTelegramChats } from "./telegram/get-telegram-chat-id.mjs";
|
|
63
64
|
|
|
64
65
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
65
66
|
|
|
@@ -89,6 +90,14 @@ function getVersion() {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
function formatTelegramChatChoice(chat) {
|
|
94
|
+
const parts = [String(chat.id)];
|
|
95
|
+
if (chat.type) parts.push(chat.type);
|
|
96
|
+
if (chat.username) parts.push(`@${chat.username}`);
|
|
97
|
+
if (chat.title) parts.push(chat.title);
|
|
98
|
+
return parts.join(" · ");
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
function hasSetupMarkers(dir) {
|
|
93
102
|
const markers = [
|
|
94
103
|
".env",
|
|
@@ -3883,32 +3892,26 @@ async function main() {
|
|
|
3883
3892
|
// Try to fetch chat ID from Telegram API
|
|
3884
3893
|
info("Fetching your chat ID from Telegram...");
|
|
3885
3894
|
try {
|
|
3886
|
-
const
|
|
3887
|
-
|
|
3888
|
-
)
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
const
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
"Couldn't find a chat ID. Make sure you sent a message to your bot.",
|
|
3903
|
-
);
|
|
3904
|
-
env.TELEGRAM_CHAT_ID = await prompt.ask(
|
|
3905
|
-
"Enter chat ID manually",
|
|
3906
|
-
"",
|
|
3907
|
-
);
|
|
3908
|
-
}
|
|
3895
|
+
const discovery = await discoverTelegramChats(env.TELEGRAM_BOT_TOKEN);
|
|
3896
|
+
|
|
3897
|
+
if (discovery.chats.length === 1) {
|
|
3898
|
+
env.TELEGRAM_CHAT_ID = String(discovery.chats[0].id);
|
|
3899
|
+
info(`✓ Found your chat ID: ${env.TELEGRAM_CHAT_ID}`);
|
|
3900
|
+
console.log();
|
|
3901
|
+
} else if (discovery.chats.length > 1) {
|
|
3902
|
+
const selectedIdx = await prompt.choose(
|
|
3903
|
+
"Select the chat Bosun should use:",
|
|
3904
|
+
discovery.chats.map(formatTelegramChatChoice),
|
|
3905
|
+
0,
|
|
3906
|
+
);
|
|
3907
|
+
const selectedChat = discovery.chats[selectedIdx];
|
|
3908
|
+
env.TELEGRAM_CHAT_ID = String(selectedChat.id);
|
|
3909
|
+
info(`✓ Selected chat ID: ${env.TELEGRAM_CHAT_ID}`);
|
|
3910
|
+
console.log();
|
|
3909
3911
|
} else {
|
|
3910
3912
|
warn(
|
|
3911
|
-
|
|
3913
|
+
discovery.message ||
|
|
3914
|
+
"Couldn't find a chat ID. Make sure you sent a message to your bot.",
|
|
3912
3915
|
);
|
|
3913
3916
|
console.log(
|
|
3914
3917
|
chalk.dim(
|
package/shell/codex-shell.mjs
CHANGED
|
@@ -31,10 +31,12 @@ const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
|
31
31
|
// ── Configuration ────────────────────────────────────────────────────────────
|
|
32
32
|
|
|
33
33
|
const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000; // 60 min for agentic tasks (matches Azure stream timeout)
|
|
34
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647; // Node.js timer clamp (2^31 - 1)
|
|
34
35
|
// MAX_STREAM_RETRIES, isTransientStreamError, streamRetryDelay ← imported from ./stream-resilience.mjs
|
|
35
36
|
const STATE_FILE = resolve(__dirname, "..", "logs", "codex-shell-state.json");
|
|
36
37
|
const SESSIONS_DIR = resolve(__dirname, "..", "logs", "sessions");
|
|
37
38
|
const MAX_PERSISTENT_TURNS = 50;
|
|
39
|
+
const timeoutNormalizationWarningKey = new Set();
|
|
38
40
|
|
|
39
41
|
// ── Payload safety ────────────────────────────────────────────────────────────
|
|
40
42
|
// The Codex API rejects JSON bodies with malformed or oversized strings.
|
|
@@ -54,6 +56,28 @@ function parseBoundedNumber(value, fallback, min, max) {
|
|
|
54
56
|
return Math.min(Math.max(Math.trunc(num), min), max);
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
function parsePositiveTimeoutMs(value, fallback = DEFAULT_TIMEOUT_MS) {
|
|
60
|
+
const fallbackValue = Number(fallback);
|
|
61
|
+
if (!Number.isFinite(fallbackValue) || fallbackValue <= 0) {
|
|
62
|
+
throw new Error("parsePositiveTimeoutMs requires a positive finite fallback");
|
|
63
|
+
}
|
|
64
|
+
const parsed = Number(value);
|
|
65
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallbackValue;
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeTimeoutMs(value, { fallback = DEFAULT_TIMEOUT_MS, label = "timeoutMs" } = {}) {
|
|
70
|
+
const parsed = parsePositiveTimeoutMs(value, fallback);
|
|
71
|
+
const normalized = Math.min(parsed, MAX_TIMER_DELAY_MS);
|
|
72
|
+
if (normalized !== parsed && !timeoutNormalizationWarningKey.has(label)) {
|
|
73
|
+
timeoutNormalizationWarningKey.add(label);
|
|
74
|
+
console.warn(
|
|
75
|
+
`[codex-shell] ${label} ${parsed}ms exceeds Node.js timer max; clamped to ${MAX_TIMER_DELAY_MS}ms`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return normalized;
|
|
79
|
+
}
|
|
80
|
+
|
|
57
81
|
function getInternalExecutorStreamConfig() {
|
|
58
82
|
try {
|
|
59
83
|
const cfg = loadConfig();
|
|
@@ -692,6 +716,10 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
692
716
|
mode = null,
|
|
693
717
|
cwd = null,
|
|
694
718
|
} = options;
|
|
719
|
+
const normalizedTimeoutMs = normalizeTimeoutMs(timeoutMs, {
|
|
720
|
+
fallback: DEFAULT_TIMEOUT_MS,
|
|
721
|
+
label: "execCodexPrompt.timeoutMs",
|
|
722
|
+
});
|
|
695
723
|
|
|
696
724
|
agentSdk = resolveAgentSdkConfig({ reload: true });
|
|
697
725
|
if (agentSdk.primary !== "codex") {
|
|
@@ -714,7 +742,7 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
714
742
|
activeTurn = true;
|
|
715
743
|
|
|
716
744
|
try {
|
|
717
|
-
const streamSafety = resolveCodexStreamSafety(
|
|
745
|
+
const streamSafety = resolveCodexStreamSafety(normalizedTimeoutMs);
|
|
718
746
|
const requestedWorkingDirectory = normalizeWorkingDirectory(cwd);
|
|
719
747
|
|
|
720
748
|
if (!persistent) {
|
|
@@ -811,7 +839,10 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
811
839
|
// immediately fail. The total wall-clock budget is still bounded by the
|
|
812
840
|
// outer timeoutMs passed in.
|
|
813
841
|
const controller = abortController || new AbortController();
|
|
814
|
-
const timer = setTimeout(
|
|
842
|
+
const timer = setTimeout(
|
|
843
|
+
() => controller.abort("timeout"),
|
|
844
|
+
normalizedTimeoutMs,
|
|
845
|
+
);
|
|
815
846
|
|
|
816
847
|
try {
|
|
817
848
|
// Use runStreamed for real-time event streaming
|
|
@@ -939,7 +970,7 @@ export async function execCodexPrompt(userMessage, options = {}) {
|
|
|
939
970
|
const msg =
|
|
940
971
|
reason === "user_stop"
|
|
941
972
|
? ":close: Agent stopped by user."
|
|
942
|
-
: `:clock: Agent timed out after ${
|
|
973
|
+
: `:clock: Agent timed out after ${normalizedTimeoutMs / 1000}s`;
|
|
943
974
|
return { finalResponse: msg, items: [], usage: null };
|
|
944
975
|
}
|
|
945
976
|
}
|