bosun 0.29.6 → 0.29.8
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/cli.mjs +49 -0
- package/codex-config.mjs +58 -8
- package/hook-profiles.mjs +14 -10
- package/monitor.mjs +15 -2
- package/package.json +2 -1
- package/repo-config.mjs +724 -0
- package/setup.mjs +393 -97
- package/telegram-bot.mjs +1 -1
- package/update-check.mjs +41 -9
package/cli.mjs
CHANGED
|
@@ -66,6 +66,7 @@ function showHelp() {
|
|
|
66
66
|
|
|
67
67
|
COMMANDS
|
|
68
68
|
--setup Run the interactive setup wizard
|
|
69
|
+
--where Show the resolved bosun config directory
|
|
69
70
|
--doctor Validate bosun .env/config setup
|
|
70
71
|
--help Show this help
|
|
71
72
|
--version Show version
|
|
@@ -203,6 +204,48 @@ function showHelp() {
|
|
|
203
204
|
`);
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
function isWslInteropRuntime() {
|
|
208
|
+
return Boolean(
|
|
209
|
+
process.env.WSL_DISTRO_NAME ||
|
|
210
|
+
process.env.WSL_INTEROP ||
|
|
211
|
+
(process.platform === "win32" &&
|
|
212
|
+
String(process.env.HOME || "")
|
|
213
|
+
.trim()
|
|
214
|
+
.startsWith("/home/")),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function resolveConfigDirForCli() {
|
|
219
|
+
if (process.env.BOSUN_DIR) return resolve(process.env.BOSUN_DIR);
|
|
220
|
+
const preferWindowsDirs =
|
|
221
|
+
process.platform === "win32" && !isWslInteropRuntime();
|
|
222
|
+
const baseDir = preferWindowsDirs
|
|
223
|
+
? process.env.APPDATA ||
|
|
224
|
+
process.env.LOCALAPPDATA ||
|
|
225
|
+
process.env.USERPROFILE ||
|
|
226
|
+
process.env.HOME ||
|
|
227
|
+
process.cwd()
|
|
228
|
+
: process.env.HOME ||
|
|
229
|
+
process.env.XDG_CONFIG_HOME ||
|
|
230
|
+
process.env.USERPROFILE ||
|
|
231
|
+
process.env.APPDATA ||
|
|
232
|
+
process.env.LOCALAPPDATA ||
|
|
233
|
+
process.cwd();
|
|
234
|
+
return resolve(baseDir, "bosun");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function printConfigLocations() {
|
|
238
|
+
const configDir = resolveConfigDirForCli();
|
|
239
|
+
const envPath = resolve(configDir, ".env");
|
|
240
|
+
const configPath = resolve(configDir, "bosun.config.json");
|
|
241
|
+
const workspacesPath = resolve(configDir, "workspaces");
|
|
242
|
+
console.log("\n Bosun config directory");
|
|
243
|
+
console.log(` ${configDir}`);
|
|
244
|
+
console.log(` .env: ${envPath}`);
|
|
245
|
+
console.log(` bosun.config.json: ${configPath}`);
|
|
246
|
+
console.log(` workspaces: ${workspacesPath}\n`);
|
|
247
|
+
}
|
|
248
|
+
|
|
206
249
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
207
250
|
|
|
208
251
|
// ── Daemon Mode ──────────────────────────────────────────────────────────────
|
|
@@ -572,6 +615,12 @@ async function main() {
|
|
|
572
615
|
process.exit(0);
|
|
573
616
|
}
|
|
574
617
|
|
|
618
|
+
// Handle --where
|
|
619
|
+
if (args.includes("--where") || args.includes("where")) {
|
|
620
|
+
printConfigLocations();
|
|
621
|
+
process.exit(0);
|
|
622
|
+
}
|
|
623
|
+
|
|
575
624
|
// Handle desktop shortcut controls
|
|
576
625
|
if (args.includes("--desktop-shortcut")) {
|
|
577
626
|
const { installDesktopShortcut, getDesktopShortcutMethodName } =
|
package/codex-config.mjs
CHANGED
|
@@ -312,6 +312,51 @@ export function hasSandboxPermissions(toml) {
|
|
|
312
312
|
return /^sandbox_permissions\s*=/m.test(toml);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
function stripSandboxPermissions(toml) {
|
|
316
|
+
let next = toml.replace(
|
|
317
|
+
/^\s*#\s*Sandbox permissions.*(?:\r?\n)?/gim,
|
|
318
|
+
"",
|
|
319
|
+
);
|
|
320
|
+
next = next.replace(/^\s*sandbox_permissions\s*=.*(?:\r?\n)?/gim, "");
|
|
321
|
+
return next;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function extractSandboxPermissionsValue(toml) {
|
|
325
|
+
const match = toml.match(/^\s*sandbox_permissions\s*=\s*(.+)$/m);
|
|
326
|
+
if (!match) return "";
|
|
327
|
+
const raw = String(match[1] || "").split("#")[0].trim();
|
|
328
|
+
if (!raw) return "";
|
|
329
|
+
if (raw.startsWith("[")) {
|
|
330
|
+
const values = parseTomlArrayLiteral(raw);
|
|
331
|
+
return values.join(",");
|
|
332
|
+
}
|
|
333
|
+
const quoted = raw.match(/^"(.*)"$/) || raw.match(/^'(.*)'$/);
|
|
334
|
+
if (quoted) return quoted[1];
|
|
335
|
+
return raw;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function insertTopLevelSandboxPermissions(toml, permValue) {
|
|
339
|
+
const block = buildSandboxPermissions(permValue).trim();
|
|
340
|
+
const tableIdx = toml.search(/^\s*\[/m);
|
|
341
|
+
if (tableIdx === -1) {
|
|
342
|
+
return `${toml.trimEnd()}\n\n${block}\n`;
|
|
343
|
+
}
|
|
344
|
+
const head = toml.slice(0, tableIdx).trimEnd();
|
|
345
|
+
const tail = toml.slice(tableIdx).trimStart();
|
|
346
|
+
return `${head}\n\n${block}\n\n${tail}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function ensureTopLevelSandboxPermissions(toml, envValue) {
|
|
350
|
+
const existingValue = extractSandboxPermissionsValue(toml);
|
|
351
|
+
const permValue = envValue || existingValue || "disk-full-write-access";
|
|
352
|
+
const stripped = stripSandboxPermissions(toml);
|
|
353
|
+
const updated = insertTopLevelSandboxPermissions(stripped, permValue);
|
|
354
|
+
return {
|
|
355
|
+
toml: updated,
|
|
356
|
+
changed: updated !== toml,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
315
360
|
/**
|
|
316
361
|
* Build a [features] block with the recommended flags.
|
|
317
362
|
* Reads environment overrides: set CODEX_FEATURES_<NAME>=false to disable.
|
|
@@ -407,16 +452,18 @@ export function ensureFeatureFlags(toml, envOverrides = process.env) {
|
|
|
407
452
|
|
|
408
453
|
/**
|
|
409
454
|
* Build the sandbox_permissions top-level key.
|
|
410
|
-
* Default:
|
|
455
|
+
* Default: "disk-full-write-access" for agentic workloads.
|
|
456
|
+
*
|
|
457
|
+
* Codex CLI expects sandbox_permissions as a plain string, NOT an array.
|
|
411
458
|
*
|
|
412
459
|
* @param {string} [envValue] CODEX_SANDBOX_PERMISSIONS env var value
|
|
413
460
|
* @returns {string} TOML line(s)
|
|
414
461
|
*/
|
|
415
462
|
export function buildSandboxPermissions(envValue) {
|
|
416
|
-
const
|
|
417
|
-
? envValue.split(",").map((s) =>
|
|
418
|
-
:
|
|
419
|
-
return `\n# Sandbox permissions (added by bosun)\nsandbox_permissions =
|
|
463
|
+
const perm = envValue
|
|
464
|
+
? envValue.split(",").map((s) => s.trim()).filter(Boolean).join(",")
|
|
465
|
+
: "disk-full-write-access";
|
|
466
|
+
return `\n# Sandbox permissions (added by bosun)\nsandbox_permissions = "${perm}"\n`;
|
|
420
467
|
}
|
|
421
468
|
|
|
422
469
|
function parseTomlArrayLiteral(raw) {
|
|
@@ -1289,10 +1336,13 @@ export function ensureCodexConfig({
|
|
|
1289
1336
|
|
|
1290
1337
|
// ── 1e. Ensure sandbox permissions ────────────────────────
|
|
1291
1338
|
|
|
1292
|
-
|
|
1339
|
+
{
|
|
1293
1340
|
const envPerms = env.CODEX_SANDBOX_PERMISSIONS || "";
|
|
1294
|
-
|
|
1295
|
-
|
|
1341
|
+
const ensured = ensureTopLevelSandboxPermissions(toml, envPerms);
|
|
1342
|
+
if (ensured.changed) {
|
|
1343
|
+
toml = ensured.toml;
|
|
1344
|
+
result.sandboxAdded = true;
|
|
1345
|
+
}
|
|
1296
1346
|
}
|
|
1297
1347
|
|
|
1298
1348
|
// ── 1f. Ensure sandbox workspace-write defaults ───────────
|
package/hook-profiles.mjs
CHANGED
|
@@ -552,6 +552,10 @@ function buildDisableEnv(hookConfig) {
|
|
|
552
552
|
};
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
+
function normalizePathForOutput(value) {
|
|
556
|
+
return String(value || "").replace(/\\/g, "/");
|
|
557
|
+
}
|
|
558
|
+
|
|
555
559
|
export function scaffoldAgentHookFiles(repoRoot, options = {}) {
|
|
556
560
|
const root = resolve(repoRoot || process.cwd());
|
|
557
561
|
const targets = normalizeHookTargets(options.targets);
|
|
@@ -585,13 +589,13 @@ export function scaffoldAgentHookFiles(repoRoot, options = {}) {
|
|
|
585
589
|
const codexPath = resolve(root, ".codex", "hooks.json");
|
|
586
590
|
const existedBefore = existsSync(codexPath);
|
|
587
591
|
if (existedBefore && !overwriteExisting) {
|
|
588
|
-
result.skipped.push(relative(root, codexPath));
|
|
592
|
+
result.skipped.push(normalizePathForOutput(relative(root, codexPath)));
|
|
589
593
|
} else {
|
|
590
594
|
writeJson(codexPath, codexHookConfig);
|
|
591
595
|
if (existedBefore) {
|
|
592
|
-
result.updated.push(relative(root, codexPath));
|
|
596
|
+
result.updated.push(normalizePathForOutput(relative(root, codexPath)));
|
|
593
597
|
} else {
|
|
594
|
-
result.written.push(relative(root, codexPath));
|
|
598
|
+
result.written.push(normalizePathForOutput(relative(root, codexPath)));
|
|
595
599
|
}
|
|
596
600
|
}
|
|
597
601
|
}
|
|
@@ -610,18 +614,18 @@ export function scaffoldAgentHookFiles(repoRoot, options = {}) {
|
|
|
610
614
|
hasLegacyBridgeInCopilotConfig(existingCopilot);
|
|
611
615
|
|
|
612
616
|
if (existedBefore && !overwriteExisting && !forceLegacyMigration) {
|
|
613
|
-
result.skipped.push(relative(root, copilotPath));
|
|
617
|
+
result.skipped.push(normalizePathForOutput(relative(root, copilotPath)));
|
|
614
618
|
} else {
|
|
615
619
|
writeJson(copilotPath, config);
|
|
616
620
|
if (existedBefore) {
|
|
617
|
-
result.updated.push(relative(root, copilotPath));
|
|
621
|
+
result.updated.push(normalizePathForOutput(relative(root, copilotPath)));
|
|
618
622
|
if (forceLegacyMigration) {
|
|
619
623
|
result.warnings.push(
|
|
620
|
-
`${relative(root, copilotPath)} contained legacy bridge path and was auto-updated`,
|
|
624
|
+
`${normalizePathForOutput(relative(root, copilotPath))} contained legacy bridge path and was auto-updated`,
|
|
621
625
|
);
|
|
622
626
|
}
|
|
623
627
|
} else {
|
|
624
|
-
result.written.push(relative(root, copilotPath));
|
|
628
|
+
result.written.push(normalizePathForOutput(relative(root, copilotPath)));
|
|
625
629
|
}
|
|
626
630
|
}
|
|
627
631
|
}
|
|
@@ -634,15 +638,15 @@ export function scaffoldAgentHookFiles(repoRoot, options = {}) {
|
|
|
634
638
|
|
|
635
639
|
if (existing === null && existsSync(claudePath)) {
|
|
636
640
|
result.warnings.push(
|
|
637
|
-
`${relative(root, claudePath)} exists but is not valid JSON; skipped`,
|
|
641
|
+
`${normalizePathForOutput(relative(root, claudePath))} exists but is not valid JSON; skipped`,
|
|
638
642
|
);
|
|
639
643
|
} else {
|
|
640
644
|
const merged = mergeClaudeSettings(existing, generated);
|
|
641
645
|
writeJson(claudePath, merged);
|
|
642
646
|
if (existedBefore) {
|
|
643
|
-
result.updated.push(relative(root, claudePath));
|
|
647
|
+
result.updated.push(normalizePathForOutput(relative(root, claudePath)));
|
|
644
648
|
} else {
|
|
645
|
-
result.written.push(relative(root, claudePath));
|
|
649
|
+
result.written.push(normalizePathForOutput(relative(root, claudePath)));
|
|
646
650
|
}
|
|
647
651
|
}
|
|
648
652
|
}
|
package/monitor.mjs
CHANGED
|
@@ -216,6 +216,7 @@ import {
|
|
|
216
216
|
setKanbanBackend,
|
|
217
217
|
listTasks as listKanbanTasks,
|
|
218
218
|
updateTaskStatus as updateKanbanTaskStatus,
|
|
219
|
+
updateTask as updateKanbanTask,
|
|
219
220
|
listProjects as listKanbanProjects,
|
|
220
221
|
createTask as createKanbanTask,
|
|
221
222
|
} from "./kanban-adapter.mjs";
|
|
@@ -9026,8 +9027,20 @@ async function triggerTaskPlannerViaKanban(
|
|
|
9026
9027
|
`[monitor] task planner task already exists in backlog — skipping: "${existingPlanner.title}" (${existingPlanner.id})`,
|
|
9027
9028
|
);
|
|
9028
9029
|
// Best-effort: keep backlog task aligned with current requirements
|
|
9029
|
-
//
|
|
9030
|
-
|
|
9030
|
+
// Update description if the backend supports it, so the agent gets fresh context
|
|
9031
|
+
try {
|
|
9032
|
+
await updateKanbanTask(existingPlanner.id, {
|
|
9033
|
+
description: desiredDescription,
|
|
9034
|
+
});
|
|
9035
|
+
console.log(
|
|
9036
|
+
`[monitor] updated description of existing planner task: "${existingPlanner.title}" (${existingPlanner.id})`,
|
|
9037
|
+
);
|
|
9038
|
+
} catch (updateErr) {
|
|
9039
|
+
// Not all backends support partial description updates — log and continue
|
|
9040
|
+
console.log(
|
|
9041
|
+
`[monitor] could not update existing planner task description (${updateErr.message || updateErr}) — skipping`,
|
|
9042
|
+
);
|
|
9043
|
+
}
|
|
9031
9044
|
|
|
9032
9045
|
const taskUrl = buildTaskUrl(existingPlanner, projectId);
|
|
9033
9046
|
if (notify) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.8",
|
|
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",
|
|
@@ -151,6 +151,7 @@
|
|
|
151
151
|
"prepublish-check.mjs",
|
|
152
152
|
"presence.mjs",
|
|
153
153
|
"primary-agent.mjs",
|
|
154
|
+
"repo-config.mjs",
|
|
154
155
|
"repo-root.mjs",
|
|
155
156
|
"restart-controller.mjs",
|
|
156
157
|
"review-agent.mjs",
|