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/cli.mjs
CHANGED
|
@@ -77,12 +77,14 @@ function showHelp() {
|
|
|
77
77
|
bosun [options]
|
|
78
78
|
|
|
79
79
|
COMMANDS
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
workflow list List declarative pipeline workflows
|
|
81
|
+
workflow run <name> Run a declarative pipeline workflow
|
|
82
|
+
--setup Launch the web-based setup wizard (default)
|
|
82
83
|
--setup-terminal Run the legacy terminal setup wizard
|
|
83
84
|
--where Show the resolved bosun config directory
|
|
84
85
|
--doctor Validate bosun .env/config setup
|
|
85
86
|
--tool-log <ID|list|prune> Retrieve/list/prune cached tool outputs
|
|
87
|
+
node:create <name> Scaffold a custom workflow node in custom-nodes/
|
|
86
88
|
--context-index [mode] Run context index workflow (run|status|search)
|
|
87
89
|
--context-index-query <text> Query text for context index search mode
|
|
88
90
|
--context-index-limit <n> Max results for context index search (default: 25)
|
|
@@ -159,6 +161,12 @@ function showHelp() {
|
|
|
159
161
|
|
|
160
162
|
Run 'bosun task --help' for complete task CLI documentation and examples.
|
|
161
163
|
|
|
164
|
+
WORKFLOWS
|
|
165
|
+
workflow list List built-in and configured workflows
|
|
166
|
+
workflow run <name> Run a declarative fresh-context workflow
|
|
167
|
+
|
|
168
|
+
Run 'bosun workflow --help' for workflow CLI examples.
|
|
169
|
+
|
|
162
170
|
VIBE-KANBAN
|
|
163
171
|
--no-vk-spawn Don't auto-spawn Vibe-Kanban
|
|
164
172
|
--vk-ensure-interval <ms> VK health check interval (default: 60000)
|
|
@@ -918,6 +926,19 @@ function daemonStatus() {
|
|
|
918
926
|
}
|
|
919
927
|
console.log(` Run --terminate to stop restart owners, then --daemon to restart.`);
|
|
920
928
|
} else {
|
|
929
|
+
const existingMonitorOwner = detectExistingMonitorLockOwner();
|
|
930
|
+
if (existingMonitorOwner) {
|
|
931
|
+
console.log(
|
|
932
|
+
` bosun daemon is not running in daemon mode, but bosun monitor is active (PID ${existingMonitorOwner.pid}).`,
|
|
933
|
+
);
|
|
934
|
+
console.log(
|
|
935
|
+
` Bosun is running in monitor mode with lock file ${existingMonitorOwner.pidFile}.`,
|
|
936
|
+
);
|
|
937
|
+
console.log(
|
|
938
|
+
` Use 'bosun --terminate' to stop it, or 'bosun --daemon' only after it is fully stopped.`,
|
|
939
|
+
);
|
|
940
|
+
process.exit(0);
|
|
941
|
+
}
|
|
921
942
|
// Broader scan: portal, monitor, ui-server, etc. (non-daemon bosun processes)
|
|
922
943
|
const allPids = findAllBosunProcessPids();
|
|
923
944
|
if (allPids.length > 0) {
|
|
@@ -1321,7 +1342,21 @@ async function main() {
|
|
|
1321
1342
|
process.exit(0);
|
|
1322
1343
|
}
|
|
1323
1344
|
|
|
1324
|
-
|
|
1345
|
+
const workflowFlagIndex = args.indexOf("--workflow");
|
|
1346
|
+
const workflowCommandIndex =
|
|
1347
|
+
args[0] === "workflow"
|
|
1348
|
+
? 0
|
|
1349
|
+
: args[0]?.startsWith("--")
|
|
1350
|
+
? args.indexOf("workflow")
|
|
1351
|
+
: -1;
|
|
1352
|
+
if (workflowCommandIndex >= 0 || workflowFlagIndex >= 0) {
|
|
1353
|
+
const { runWorkflowCli } = await import("./workflow/workflow-cli.mjs");
|
|
1354
|
+
const commandStartIndex = workflowCommandIndex >= 0 ? workflowCommandIndex : workflowFlagIndex;
|
|
1355
|
+
const workflowArgs = args.slice(commandStartIndex + 1);
|
|
1356
|
+
await runWorkflowCli(workflowArgs);
|
|
1357
|
+
process.exit(0);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1325
1360
|
const auditFlagIndex = args.indexOf("--audit");
|
|
1326
1361
|
const auditCommandIndex =
|
|
1327
1362
|
args[0] === "audit"
|
|
@@ -1333,8 +1368,27 @@ async function main() {
|
|
|
1333
1368
|
const { runAuditCli } = await import("./lib/codebase-audit.mjs");
|
|
1334
1369
|
const commandStartIndex = auditCommandIndex >= 0 ? auditCommandIndex : auditFlagIndex;
|
|
1335
1370
|
const auditArgs = args.slice(commandStartIndex + 1);
|
|
1336
|
-
|
|
1337
|
-
process.exit(
|
|
1371
|
+
await runAuditCli(auditArgs);
|
|
1372
|
+
process.exit(0);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
if (args[0] === "node:create" || (args[0] === "node" && args[1] === "create")) {
|
|
1376
|
+
const name = args[0] === "node:create" ? args[1] : args[2];
|
|
1377
|
+
if (!name) {
|
|
1378
|
+
console.error("Usage: bosun node:create <name>");
|
|
1379
|
+
process.exit(1);
|
|
1380
|
+
}
|
|
1381
|
+
const { scaffoldCustomNodeFile } = await import("./workflow/workflow-nodes.mjs");
|
|
1382
|
+
try {
|
|
1383
|
+
const result = scaffoldCustomNodeFile(name, { repoRoot: runtimeRepoRoot });
|
|
1384
|
+
console.log(`\n ✓ Created custom node \"${result.type}\"`);
|
|
1385
|
+
console.log(` File: ${result.filePath}`);
|
|
1386
|
+
console.log("");
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
console.error(` Error: ${err.message}`);
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
process.exit(0);
|
|
1338
1392
|
}
|
|
1339
1393
|
|
|
1340
1394
|
// Handle --help
|
package/config/config.mjs
CHANGED
|
@@ -26,12 +26,13 @@ import {
|
|
|
26
26
|
} from "../agent/agent-prompts.mjs";
|
|
27
27
|
import { resolveAgentRepoRoot, resolveRepoLocalBosunDir } from "./repo-root.mjs";
|
|
28
28
|
import { applyAllCompatibility } from "../compat.mjs";
|
|
29
|
+
import { ensureTestRuntimeSandbox } from "../infra/test-runtime.mjs";
|
|
29
30
|
import {
|
|
30
31
|
normalizeExecutorKey,
|
|
31
32
|
getModelsForExecutor,
|
|
32
33
|
MODEL_ALIASES,
|
|
33
34
|
} from "../task/task-complexity.mjs";
|
|
34
|
-
import {
|
|
35
|
+
import { normalizePipelineWorkflows } from "../workflow/pipeline-workflows.mjs";
|
|
35
36
|
|
|
36
37
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
37
38
|
|
|
@@ -635,6 +636,86 @@ function resolveTriggerSystemConfig(configData, defaults) {
|
|
|
635
636
|
});
|
|
636
637
|
}
|
|
637
638
|
|
|
639
|
+
function toBoundedInt(value, fallback, min, max) {
|
|
640
|
+
const parsed = Number(value);
|
|
641
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
642
|
+
const rounded = Math.trunc(parsed);
|
|
643
|
+
return Math.min(max, Math.max(min, rounded));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function normalizeStatusList(rawStates) {
|
|
647
|
+
const source = Array.isArray(rawStates)
|
|
648
|
+
? rawStates
|
|
649
|
+
: String(rawStates || "").split(",");
|
|
650
|
+
const values = [];
|
|
651
|
+
for (const raw of source) {
|
|
652
|
+
const normalized = String(raw || "").trim().toLowerCase();
|
|
653
|
+
if (!normalized || values.includes(normalized)) continue;
|
|
654
|
+
values.push(normalized);
|
|
655
|
+
}
|
|
656
|
+
return values.length > 0 ? values : ["done", "cancelled"];
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function resolveWorkflowConfig(configData = {}) {
|
|
660
|
+
const rawWorkflows = configData?.workflows;
|
|
661
|
+
const rawEntries = Array.isArray(rawWorkflows) ? rawWorkflows : [];
|
|
662
|
+
const normalized = [];
|
|
663
|
+
|
|
664
|
+
for (const rawEntry of rawEntries) {
|
|
665
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) continue;
|
|
666
|
+
const type = String(rawEntry.type || "").trim().toLowerCase();
|
|
667
|
+
if (!type) continue;
|
|
668
|
+
|
|
669
|
+
const entryBase = {
|
|
670
|
+
type,
|
|
671
|
+
enabled: rawEntry.enabled !== false,
|
|
672
|
+
name: String(rawEntry.name || "").trim() || null,
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
if (type !== "continuation-loop") {
|
|
676
|
+
normalized.push(Object.freeze(entryBase));
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const onStuckRaw = String(rawEntry.onStuck || "escalate").trim().toLowerCase();
|
|
681
|
+
const onStuck = ["retry", "escalate", "pause"].includes(onStuckRaw)
|
|
682
|
+
? onStuckRaw
|
|
683
|
+
: "escalate";
|
|
684
|
+
|
|
685
|
+
normalized.push(Object.freeze({
|
|
686
|
+
...entryBase,
|
|
687
|
+
taskId: String(rawEntry.taskId || "").trim(),
|
|
688
|
+
worktreePath: String(rawEntry.worktreePath || "").trim(),
|
|
689
|
+
maxTurns: toBoundedInt(rawEntry.maxTurns, 8, 1, 1000),
|
|
690
|
+
pollIntervalMs: toBoundedInt(rawEntry.pollIntervalMs, 30000, 1000, 3600000),
|
|
691
|
+
terminalStates: normalizeStatusList(rawEntry.terminalStates),
|
|
692
|
+
stuckThresholdMs: toBoundedInt(rawEntry.stuckThresholdMs, 300000, 1000, 86400000),
|
|
693
|
+
onStuck,
|
|
694
|
+
continuePrompt: String(rawEntry.continuePrompt || "").trim(),
|
|
695
|
+
retryPrompt: String(rawEntry.retryPrompt || "").trim(),
|
|
696
|
+
sdk: String(rawEntry.sdk || "auto").trim() || "auto",
|
|
697
|
+
model: String(rawEntry.model || "").trim(),
|
|
698
|
+
timeoutMs: toBoundedInt(rawEntry.timeoutMs, 1800000, 1000, 21600000),
|
|
699
|
+
}));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Support declarative pipeline workflow maps while keeping backward-compatible
|
|
703
|
+
// array semantics for continuation-loop style entries.
|
|
704
|
+
if (rawWorkflows && typeof rawWorkflows === "object" && !Array.isArray(rawWorkflows)) {
|
|
705
|
+
const namedWorkflows = normalizePipelineWorkflows(rawWorkflows);
|
|
706
|
+
for (const [workflowName, workflowDefinition] of Object.entries(namedWorkflows)) {
|
|
707
|
+
Object.defineProperty(normalized, workflowName, {
|
|
708
|
+
value: workflowDefinition,
|
|
709
|
+
enumerable: true,
|
|
710
|
+
configurable: false,
|
|
711
|
+
writable: false,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return Object.freeze(normalized);
|
|
717
|
+
}
|
|
718
|
+
|
|
638
719
|
// ── Git helpers ──────────────────────────────────────────────────────────────
|
|
639
720
|
|
|
640
721
|
function detectRepoSlug(repoRoot = "") {
|
|
@@ -1404,6 +1485,7 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1404
1485
|
process.env.BOSUN_LOAD_REPO_ENV_WITH_EXPLICIT_CONFIG,
|
|
1405
1486
|
false,
|
|
1406
1487
|
);
|
|
1488
|
+
const envOverride = reloadEnv || !isEnvEnabled(process.env.BOSUN_ENV_NO_OVERRIDE, false);
|
|
1407
1489
|
let detectedRepoRoot = "";
|
|
1408
1490
|
const getFallbackRepoRoot = () => {
|
|
1409
1491
|
if (normalizedRepoRootOverride) return normalizedRepoRootOverride;
|
|
@@ -1412,10 +1494,23 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1412
1494
|
};
|
|
1413
1495
|
|
|
1414
1496
|
// Determine config directory (where bosun stores its config)
|
|
1415
|
-
|
|
1497
|
+
let configDir =
|
|
1416
1498
|
explicitConfigDirRaw ||
|
|
1417
1499
|
resolveConfigDir(normalizedRepoRootOverride);
|
|
1418
1500
|
|
|
1501
|
+
// If BOSUN_HOME/BOSUN_DIR is declared in the repo-local .env, load that first
|
|
1502
|
+
// and then pivot into the explicit config dir before reading config files.
|
|
1503
|
+
if (!hasExplicitConfigDir) {
|
|
1504
|
+
loadDotEnv(configDir, { override: envOverride });
|
|
1505
|
+
const envConfigDirRaw = process.env.BOSUN_HOME || process.env.BOSUN_DIR || "";
|
|
1506
|
+
if (String(envConfigDirRaw).trim()) {
|
|
1507
|
+
const resolvedEnvConfigDir = resolve(envConfigDirRaw);
|
|
1508
|
+
if (resolvedEnvConfigDir !== resolve(configDir)) {
|
|
1509
|
+
configDir = resolvedEnvConfigDir;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1419
1514
|
const configFile = loadConfigFile(configDir);
|
|
1420
1515
|
let configData = configFile.data || {};
|
|
1421
1516
|
const configFileHadInvalidJson = configFile.error === "invalid-json";
|
|
@@ -1475,7 +1570,6 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1475
1570
|
// for Bosun-specific configuration, so it should override any stale shell
|
|
1476
1571
|
// env vars. Users who want shell vars to take precedence can use profiles
|
|
1477
1572
|
// or set BOSUN_ENV_NO_OVERRIDE=1.
|
|
1478
|
-
const envOverride = reloadEnv || !isEnvEnabled(process.env.BOSUN_ENV_NO_OVERRIDE, false);
|
|
1479
1573
|
loadDotEnv(configDir, { override: envOverride });
|
|
1480
1574
|
|
|
1481
1575
|
const shouldLoadRepoEnv =
|
|
@@ -1963,6 +2057,17 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1963
2057
|
internalExecutorConfig.maxRetries ||
|
|
1964
2058
|
2,
|
|
1965
2059
|
),
|
|
2060
|
+
retryReviewThreshold: Number(
|
|
2061
|
+
process.env.INTERNAL_EXECUTOR_RETRY_REVIEW_THRESHOLD ||
|
|
2062
|
+
internalExecutorConfig.retryReviewThreshold ||
|
|
2063
|
+
internalExecutorConfig.maxRetries ||
|
|
2064
|
+
3,
|
|
2065
|
+
),
|
|
2066
|
+
retryDelayMs: Number(
|
|
2067
|
+
process.env.INTERNAL_EXECUTOR_RETRY_DELAY_MS ||
|
|
2068
|
+
internalExecutorConfig.retryDelayMs ||
|
|
2069
|
+
15000,
|
|
2070
|
+
),
|
|
1966
2071
|
autoCreatePr: internalExecutorConfig.autoCreatePr !== false,
|
|
1967
2072
|
projectId:
|
|
1968
2073
|
process.env.INTERNAL_EXECUTOR_PROJECT_ID ||
|
|
@@ -2105,6 +2210,7 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
2105
2210
|
configData,
|
|
2106
2211
|
triggerSystemDefaults,
|
|
2107
2212
|
);
|
|
2213
|
+
const workflows = resolveWorkflowConfig(configData);
|
|
2108
2214
|
|
|
2109
2215
|
// ── GitHub Reconciler ───────────────────────────────────
|
|
2110
2216
|
const ghReconcileEnabled = isEnvEnabled(
|
|
@@ -2189,6 +2295,21 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
2189
2295
|
assessWithSdk,
|
|
2190
2296
|
});
|
|
2191
2297
|
|
|
2298
|
+
const workflowDefaults =
|
|
2299
|
+
configData.workflowDefaults && typeof configData.workflowDefaults === "object"
|
|
2300
|
+
? {
|
|
2301
|
+
...configData.workflowDefaults,
|
|
2302
|
+
templates: Array.isArray(configData.workflowDefaults.templates)
|
|
2303
|
+
? [...configData.workflowDefaults.templates]
|
|
2304
|
+
: configData.workflowDefaults.templates,
|
|
2305
|
+
templateOverridesById:
|
|
2306
|
+
configData.workflowDefaults.templateOverridesById &&
|
|
2307
|
+
typeof configData.workflowDefaults.templateOverridesById === "object"
|
|
2308
|
+
? { ...configData.workflowDefaults.templateOverridesById }
|
|
2309
|
+
: {},
|
|
2310
|
+
}
|
|
2311
|
+
: {};
|
|
2312
|
+
|
|
2192
2313
|
// ── Fleet Coordination ─────────────────────────────────────
|
|
2193
2314
|
// Multi-workstation collaboration: when 2+ bosun instances share
|
|
2194
2315
|
// the same repo, the fleet system coordinates task planning, dispatch,
|
|
@@ -2371,6 +2492,7 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
2371
2492
|
telegramVerbosity,
|
|
2372
2493
|
|
|
2373
2494
|
triggerSystem,
|
|
2495
|
+
workflows,
|
|
2374
2496
|
|
|
2375
2497
|
// GitHub Reconciler
|
|
2376
2498
|
githubReconcile: {
|
|
@@ -2392,6 +2514,10 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
2392
2514
|
// Fleet Coordination
|
|
2393
2515
|
fleet,
|
|
2394
2516
|
|
|
2517
|
+
// Workflow template defaults + opt-in typed workflow entries
|
|
2518
|
+
workflowDefaults: Object.freeze(workflowDefaults),
|
|
2519
|
+
workflows,
|
|
2520
|
+
|
|
2395
2521
|
// Paths
|
|
2396
2522
|
statusPath,
|
|
2397
2523
|
telegramPollLockPath,
|
|
@@ -2407,6 +2533,7 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
2407
2533
|
workspacesDir,
|
|
2408
2534
|
activeWorkspace,
|
|
2409
2535
|
agentRepoRoot,
|
|
2536
|
+
workflows,
|
|
2410
2537
|
|
|
2411
2538
|
// Agent prompts
|
|
2412
2539
|
agentPrompts,
|