bosun 0.26.6 → 0.27.1
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/README.md +11 -8
- package/agent-hook-bridge.mjs +6 -3
- package/agent-hooks.mjs +9 -0
- package/agent-prompts.mjs +26 -0
- package/autofix.mjs +30 -6
- package/bosun.schema.json +47 -0
- package/cli.mjs +98 -1
- package/compat.mjs +54 -8
- package/config.mjs +12 -6
- package/monitor.mjs +1 -1
- package/package.json +8 -7
- package/postinstall.mjs +14 -0
- package/publish.mjs +2 -4
- package/session-tracker.mjs +13 -0
- package/setup.mjs +124 -46
- package/startup-service.mjs +1 -1
- package/task-executor.mjs +50 -3
- package/task-store.mjs +3 -0
- package/telegram-bot.mjs +49 -2
- package/ui/app.js +157 -25
- package/ui/components/kanban-board.js +56 -9
- package/ui/components/session-list.js +105 -93
- package/ui/components/workspace-switcher.js +121 -0
- package/ui/demo.html +691 -41
- package/ui/index.html +11 -3
- package/ui/modules/icons.js +34 -0
- package/ui/modules/router.js +9 -4
- package/ui/styles/base.css +18 -5
- package/ui/styles/components.css +1369 -287
- package/ui/styles/kanban.css +39 -1
- package/ui/styles/layout.css +260 -73
- package/ui/styles/sessions.css +219 -3
- package/ui/styles/variables.css +124 -52
- package/ui/styles/workspace-switcher.css +142 -0
- package/ui/tabs/agents.js +326 -181
- package/ui/tabs/chat.js +352 -12
- package/ui/tabs/control.js +438 -387
- package/ui/tabs/dashboard.js +356 -156
- package/ui/tabs/settings.js +93 -16
- package/ui/tabs/tasks.js +368 -132
- package/ui-server.mjs +183 -1
- package/update-check.mjs +18 -6
- package/ve-orchestrator.ps1 +1 -1
- package/vibe-kanban-wrapper.mjs +1 -1
- package/workspace-manager.mjs +527 -0
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# bosun
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bosun is a production-grade supervisor for AI coding agents. It routes tasks across executors, automates PR lifecycles, and keeps operators in control through Telegram, the Mini App dashboard, and optional WhatsApp notifications.
|
|
4
4
|
|
|
5
|
-
[Website](https://bosun.virtengine.com) · [Docs](https://bosun.virtengine.com/docs/) · [GitHub](https://github.com/virtengine/
|
|
5
|
+
[Website](https://bosun.virtengine.com) · [Docs](https://bosun.virtengine.com/docs/) · [GitHub](https://github.com/virtengine/bosun?tab=readme-ov-file#bosun) · [npm](https://www.npmjs.com/package/bosun) · [Issues](https://github.com/virtengine/bosun/issues)
|
|
6
6
|
|
|
7
|
-

|
|
9
|
-
](https://github.com/virtengine/bosun/actions/workflows/ci.yaml)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://www.npmjs.com/package/bosun)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Quick start
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install -g
|
|
16
|
+
npm install -g bosun
|
|
17
17
|
cd your-repo
|
|
18
18
|
bosun
|
|
19
19
|
```
|
|
@@ -46,7 +46,7 @@ Requires:
|
|
|
46
46
|
|
|
47
47
|
**Published docs (website):** https://bosun.virtengine.com/docs/
|
|
48
48
|
|
|
49
|
-
**Source docs (markdown):** `_docs/` is the source of truth for long-form documentation.
|
|
49
|
+
**Source docs (markdown):** `_docs/` is the source of truth for long-form documentation. The website should be generated from the same markdown content so docs stay in sync.
|
|
50
50
|
|
|
51
51
|
Key references:
|
|
52
52
|
- [GitHub adapter enhancements](_docs/KANBAN_GITHUB_ENHANCEMENT.md)
|
|
@@ -78,6 +78,9 @@ npm -C scripts/bosun test
|
|
|
78
78
|
|
|
79
79
|
# Prepublish safety checks
|
|
80
80
|
npm -C scripts/bosun run prepublishOnly
|
|
81
|
+
|
|
82
|
+
# Install local git hooks (pre-commit + pre-push)
|
|
83
|
+
npm -C scripts/bosun run hooks:install
|
|
81
84
|
```
|
|
82
85
|
|
|
83
86
|
---
|
package/agent-hook-bridge.mjs
CHANGED
|
@@ -187,14 +187,17 @@ async function run() {
|
|
|
187
187
|
for (const item of mapped) {
|
|
188
188
|
const context = {
|
|
189
189
|
sdk: agent,
|
|
190
|
-
taskId: process.env.VE_TASK_ID || "",
|
|
191
|
-
taskTitle:
|
|
190
|
+
taskId: process.env.VE_TASK_ID || process.env.BOSUN_TASK_ID || "",
|
|
191
|
+
taskTitle:
|
|
192
|
+
process.env.VE_TASK_TITLE || process.env.BOSUN_TASK_TITLE || "",
|
|
192
193
|
taskDescription:
|
|
193
194
|
process.env.VE_TASK_DESCRIPTION ||
|
|
195
|
+
process.env.BOSUN_TASK_DESCRIPTION ||
|
|
194
196
|
process.env.VE_DESCRIPTION ||
|
|
195
197
|
process.env.VK_DESCRIPTION ||
|
|
196
198
|
"",
|
|
197
|
-
branch:
|
|
199
|
+
branch:
|
|
200
|
+
process.env.VE_BRANCH_NAME || process.env.BOSUN_BRANCH_NAME || "",
|
|
198
201
|
worktreePath: process.cwd(),
|
|
199
202
|
extra: {
|
|
200
203
|
source_event: sourceEvent,
|
package/agent-hooks.mjs
CHANGED
|
@@ -817,6 +817,15 @@ function _buildEnv(ctx) {
|
|
|
817
817
|
VE_SDK: ctx.sdk ?? "",
|
|
818
818
|
VE_REPO_ROOT: ctx.repoRoot ?? REPO_ROOT,
|
|
819
819
|
VE_HOOK_BLOCKING: "false", // Overridden per-hook in execution
|
|
820
|
+
// ── Bosun canonical aliases (always set so agents see a consistent set) ──
|
|
821
|
+
BOSUN_TASK_ID: ctx.taskId ?? "",
|
|
822
|
+
BOSUN_TASK_TITLE: ctx.taskTitle ?? "",
|
|
823
|
+
BOSUN_TASK_DESCRIPTION: ctx.taskDescription ?? "",
|
|
824
|
+
BOSUN_BRANCH_NAME: ctx.branch ?? "",
|
|
825
|
+
BOSUN_WORKTREE_PATH: ctx.worktreePath ?? "",
|
|
826
|
+
BOSUN_SDK: ctx.sdk ?? "",
|
|
827
|
+
BOSUN_MANAGED: "1",
|
|
828
|
+
VE_MANAGED: "1",
|
|
820
829
|
};
|
|
821
830
|
|
|
822
831
|
// Merge any extra context values as env vars
|
package/agent-prompts.mjs
CHANGED
|
@@ -227,6 +227,32 @@ You are the always-on reliability guardian for bosun in devmode.
|
|
|
227
227
|
- No placeholders/stubs/TODO-only output.
|
|
228
228
|
- Keep behavior stable and production-safe.
|
|
229
229
|
|
|
230
|
+
## Bosun Task Agent — Git & PR Workflow
|
|
231
|
+
|
|
232
|
+
You are running as a **Bosun-managed task agent**. Environment variables
|
|
233
|
+
\`BOSUN_TASK_TITLE\`, \`BOSUN_BRANCH_NAME\`, \`BOSUN_TASK_ID\`, and their
|
|
234
|
+
\`VE_*\` / \`VK_*\` aliases are available in your environment.
|
|
235
|
+
|
|
236
|
+
**Before committing:**
|
|
237
|
+
- Run auto-formatting tools (gofmt, prettier, etc.) relevant to changed files.
|
|
238
|
+
- Fix any lint or vet warnings introduced by your changes.
|
|
239
|
+
|
|
240
|
+
**After committing:**
|
|
241
|
+
- If a precommit hook auto-applies additional formatting changes, add those
|
|
242
|
+
to a follow-up commit before pushing.
|
|
243
|
+
- Merge any upstream changes from the base branch before pushing:
|
|
244
|
+
\`git fetch origin && git merge origin/<base-branch> --no-edit\`
|
|
245
|
+
Resolve any conflicts that arise.
|
|
246
|
+
- Push: \`git push --set-upstream origin {{BRANCH}}\`
|
|
247
|
+
- After a successful push, open a Pull Request:
|
|
248
|
+
\`gh pr create --title "{{TASK_TITLE}}" --body "Closes task {{TASK_ID}}"\`
|
|
249
|
+
- **Do NOT** run \`gh pr merge\` — the orchestrator handles merges after CI.
|
|
250
|
+
|
|
251
|
+
**Do NOT:**
|
|
252
|
+
- Bypass pre-push hooks (\`git push --no-verify\` is forbidden).
|
|
253
|
+
- Use \`git add .\` — stage files individually.
|
|
254
|
+
- Wait for user confirmation before pushing or opening the PR.
|
|
255
|
+
|
|
230
256
|
## Agent Status Endpoint
|
|
231
257
|
- URL: http://127.0.0.1:{{ENDPOINT_PORT}}/api/tasks/{{TASK_ID}}
|
|
232
258
|
- POST /status {"status":"inreview"} after PR-ready push
|
package/autofix.mjs
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import { spawn, execSync } from "node:child_process";
|
|
31
|
-
import { existsSync, mkdirSync, createWriteStream } from "node:fs";
|
|
31
|
+
import { existsSync, mkdirSync, createWriteStream, readFileSync } from "node:fs";
|
|
32
32
|
import { readFile, writeFile } from "node:fs/promises";
|
|
33
33
|
import { resolve, dirname } from "node:path";
|
|
34
34
|
import { fileURLToPath } from "node:url";
|
|
@@ -348,12 +348,36 @@ export function isDevMode() {
|
|
|
348
348
|
return false;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
// Check for
|
|
352
|
-
|
|
351
|
+
// Check for bosun repo markers (standalone repo)
|
|
352
|
+
let repoCursor = resolve(__dirname);
|
|
353
|
+
for (let i = 0; i < 5; i += 1) {
|
|
354
|
+
const pkgPath = resolve(repoCursor, "package.json");
|
|
355
|
+
if (existsSync(pkgPath)) {
|
|
356
|
+
try {
|
|
357
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
358
|
+
if (pkg?.name === "bosun" && existsSync(resolve(repoCursor, "monitor.mjs"))) {
|
|
359
|
+
_devModeCache = true;
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
/* ignore */
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
repoCursor = resolve(repoCursor, "..");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Check for monorepo markers (source repo). Walk up a few levels to
|
|
370
|
+
// handle scripts/bosun -> repo root layout.
|
|
353
371
|
const monoRepoMarkers = ["go.mod", "Makefile", "AGENTS.md", "x"];
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
)
|
|
372
|
+
let cursor = resolve(__dirname);
|
|
373
|
+
let isMonoRepo = false;
|
|
374
|
+
for (let i = 0; i < 5; i += 1) {
|
|
375
|
+
if (monoRepoMarkers.some((m) => existsSync(resolve(cursor, m)))) {
|
|
376
|
+
isMonoRepo = true;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
cursor = resolve(cursor, "..");
|
|
380
|
+
}
|
|
357
381
|
|
|
358
382
|
_devModeCache = isMonoRepo;
|
|
359
383
|
return isMonoRepo;
|
package/bosun.schema.json
CHANGED
|
@@ -221,6 +221,53 @@
|
|
|
221
221
|
"type": "string",
|
|
222
222
|
"enum": ["codex-sdk", "kanban", "disabled"]
|
|
223
223
|
},
|
|
224
|
+
"activeWorkspace": {
|
|
225
|
+
"type": "string",
|
|
226
|
+
"description": "ID of the currently active workspace"
|
|
227
|
+
},
|
|
228
|
+
"workspaces": {
|
|
229
|
+
"type": "array",
|
|
230
|
+
"description": "Multi-repo workspace definitions",
|
|
231
|
+
"items": {
|
|
232
|
+
"type": "object",
|
|
233
|
+
"additionalProperties": true,
|
|
234
|
+
"required": ["id", "name"],
|
|
235
|
+
"properties": {
|
|
236
|
+
"id": {
|
|
237
|
+
"type": "string",
|
|
238
|
+
"description": "Unique workspace identifier (lowercase, alphanumeric, dashes)"
|
|
239
|
+
},
|
|
240
|
+
"name": {
|
|
241
|
+
"type": "string",
|
|
242
|
+
"description": "Human-readable workspace name"
|
|
243
|
+
},
|
|
244
|
+
"repos": {
|
|
245
|
+
"type": "array",
|
|
246
|
+
"description": "Repositories in this workspace",
|
|
247
|
+
"items": {
|
|
248
|
+
"type": "object",
|
|
249
|
+
"additionalProperties": true,
|
|
250
|
+
"required": ["name"],
|
|
251
|
+
"properties": {
|
|
252
|
+
"name": { "type": "string", "description": "Repository directory name" },
|
|
253
|
+
"url": { "type": "string", "description": "Git clone URL" },
|
|
254
|
+
"slug": { "type": "string", "description": "GitHub slug (org/repo)" },
|
|
255
|
+
"primary": { "type": "boolean", "description": "Whether this is the primary repo" },
|
|
256
|
+
"branch": { "type": "string", "description": "Default branch to track" }
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"activeRepo": {
|
|
261
|
+
"type": ["string", "null"],
|
|
262
|
+
"description": "Currently active repository name within the workspace"
|
|
263
|
+
},
|
|
264
|
+
"createdAt": {
|
|
265
|
+
"type": "string",
|
|
266
|
+
"description": "ISO 8601 creation timestamp"
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
},
|
|
224
271
|
"defaultRepository": { "type": "string" },
|
|
225
272
|
"repositoryDefaults": { "$ref": "#/$defs/repositoryDefaults" },
|
|
226
273
|
"repositories": {
|
package/cli.mjs
CHANGED
|
@@ -109,6 +109,12 @@ function showHelp() {
|
|
|
109
109
|
CONTAINER_ENABLED=1 Enable container isolation for agent execution
|
|
110
110
|
CONTAINER_RUNTIME=docker Runtime to use (docker|podman|container)
|
|
111
111
|
|
|
112
|
+
WORKSPACES
|
|
113
|
+
--workspace-list List configured workspaces
|
|
114
|
+
--workspace-add <name> Create a new workspace
|
|
115
|
+
--workspace-switch <id> Switch active workspace
|
|
116
|
+
--workspace-add-repo Add repo to workspace (interactive)
|
|
117
|
+
|
|
112
118
|
VIBE-KANBAN
|
|
113
119
|
--no-vk-spawn Don't auto-spawn Vibe-Kanban
|
|
114
120
|
--vk-ensure-interval <ms> VK health check interval (default: 60000)
|
|
@@ -178,7 +184,7 @@ function showHelp() {
|
|
|
178
184
|
bosun --no-codex --no-autofix # minimal mode
|
|
179
185
|
|
|
180
186
|
DOCS
|
|
181
|
-
https://www.npmjs.com/package
|
|
187
|
+
https://www.npmjs.com/package/bosun
|
|
182
188
|
`);
|
|
183
189
|
}
|
|
184
190
|
|
|
@@ -667,6 +673,97 @@ async function main() {
|
|
|
667
673
|
// agent sessions that happen to have hook config files in their tree.
|
|
668
674
|
process.env.VE_MANAGED = "1";
|
|
669
675
|
|
|
676
|
+
// Handle workspace commands
|
|
677
|
+
if (args.includes("--workspace-list") || args.includes("workspace-list")) {
|
|
678
|
+
const { listWorkspaces, getActiveWorkspace } = await import("./workspace-manager.mjs");
|
|
679
|
+
const configDirArg = getArgValue("--config-dir");
|
|
680
|
+
const configDir = configDirArg || process.env.BOSUN_DIR || resolve(os.homedir(), "bosun");
|
|
681
|
+
const workspaces = listWorkspaces(configDir);
|
|
682
|
+
const active = getActiveWorkspace(configDir);
|
|
683
|
+
if (workspaces.length === 0) {
|
|
684
|
+
console.log("\n No workspaces configured. Run 'bosun --setup' to create one.\n");
|
|
685
|
+
} else {
|
|
686
|
+
console.log("\n Workspaces:");
|
|
687
|
+
for (const ws of workspaces) {
|
|
688
|
+
const marker = ws.id === active?.id ? " ← active" : "";
|
|
689
|
+
console.log(` ${ws.name} (${ws.id})${marker}`);
|
|
690
|
+
for (const repo of ws.repos || []) {
|
|
691
|
+
const primary = repo.primary ? " [primary]" : "";
|
|
692
|
+
const exists = repo.exists ? "✓" : "✗";
|
|
693
|
+
console.log(` ${exists} ${repo.name} — ${repo.slug || repo.url || "local"}${primary}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
console.log("");
|
|
697
|
+
}
|
|
698
|
+
process.exit(0);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (args.includes("--workspace-add")) {
|
|
702
|
+
const { createWorkspace } = await import("./workspace-manager.mjs");
|
|
703
|
+
const configDirArg = getArgValue("--config-dir");
|
|
704
|
+
const configDir = configDirArg || process.env.BOSUN_DIR || resolve(os.homedir(), "bosun");
|
|
705
|
+
const name = getArgValue("--workspace-add");
|
|
706
|
+
if (!name) {
|
|
707
|
+
console.error(" Error: workspace name is required. Usage: bosun --workspace-add <name>");
|
|
708
|
+
process.exit(1);
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
const ws = createWorkspace(configDir, { name });
|
|
712
|
+
console.log(`\n ✓ Workspace "${ws.name}" created at ${ws.path}\n`);
|
|
713
|
+
} catch (err) {
|
|
714
|
+
console.error(` Error: ${err.message}`);
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
process.exit(0);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (args.includes("--workspace-switch")) {
|
|
721
|
+
const { setActiveWorkspace, getWorkspace } = await import("./workspace-manager.mjs");
|
|
722
|
+
const configDirArg = getArgValue("--config-dir");
|
|
723
|
+
const configDir = configDirArg || process.env.BOSUN_DIR || resolve(os.homedir(), "bosun");
|
|
724
|
+
const wsId = getArgValue("--workspace-switch");
|
|
725
|
+
if (!wsId) {
|
|
726
|
+
console.error(" Error: workspace ID required. Usage: bosun --workspace-switch <id>");
|
|
727
|
+
process.exit(1);
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
setActiveWorkspace(configDir, wsId);
|
|
731
|
+
const ws = getWorkspace(configDir, wsId);
|
|
732
|
+
console.log(`\n ✓ Switched to workspace "${ws?.name || wsId}"\n`);
|
|
733
|
+
} catch (err) {
|
|
734
|
+
console.error(` Error: ${err.message}`);
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
process.exit(0);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (args.includes("--workspace-add-repo")) {
|
|
741
|
+
const { addRepoToWorkspace, getActiveWorkspace, listWorkspaces } = await import("./workspace-manager.mjs");
|
|
742
|
+
const configDirArg = getArgValue("--config-dir");
|
|
743
|
+
const configDir = configDirArg || process.env.BOSUN_DIR || resolve(os.homedir(), "bosun");
|
|
744
|
+
const active = getActiveWorkspace(configDir);
|
|
745
|
+
if (!active) {
|
|
746
|
+
console.error(" No active workspace. Create one first: bosun --workspace-add <name>");
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
const url = getArgValue("--workspace-add-repo");
|
|
750
|
+
if (!url) {
|
|
751
|
+
console.error(" Error: repo URL required. Usage: bosun --workspace-add-repo <git-url>");
|
|
752
|
+
process.exit(1);
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
console.log(` Cloning into workspace "${active.name}"...`);
|
|
756
|
+
const repo = addRepoToWorkspace(configDir, active.id, { url });
|
|
757
|
+
console.log(`\n ✓ Added repo "${repo.name}" to workspace "${active.name}"`);
|
|
758
|
+
if (repo.cloned) console.log(` Cloned to: ${repo.path}`);
|
|
759
|
+
console.log("");
|
|
760
|
+
} catch (err) {
|
|
761
|
+
console.error(` Error: ${err.message}`);
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
process.exit(0);
|
|
765
|
+
}
|
|
766
|
+
|
|
670
767
|
// Handle --setup
|
|
671
768
|
if (args.includes("--setup") || args.includes("setup")) {
|
|
672
769
|
const configDirArg = getArgValue("--config-dir");
|
package/compat.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* should be imported as early as possible in the startup path.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "node:fs";
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, rmSync } from "node:fs";
|
|
15
15
|
import { resolve, join } from "node:path";
|
|
16
16
|
|
|
17
17
|
// ── Legacy config file names accepted from old codex-monitor installations ───
|
|
@@ -255,12 +255,29 @@ function rewriteEnvContent(content) {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
|
|
258
|
+
* Remove the legacy codex-monitor config directory after successful migration.
|
|
259
|
+
* Runs in a deferred setTimeout so it doesn't block startup.
|
|
260
|
+
*/
|
|
261
|
+
function scheduleLegacyCleanup(legacyDir) {
|
|
262
|
+
if (!legacyDir || !existsSync(legacyDir)) return;
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
try {
|
|
265
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
266
|
+
console.log(`[compat] Cleaned up legacy config directory: ${legacyDir}`);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.warn(
|
|
269
|
+
`[compat] Could not remove legacy directory ${legacyDir}: ${err.message}`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}, 5000);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* If BOSUN_DIR is not set but a legacy codex-monitor dir exists,
|
|
277
|
+
* automatically migrate config to ~/bosun and set BOSUN_DIR to the new location.
|
|
278
|
+
* After successful migration, schedule legacy directory removal.
|
|
261
279
|
*
|
|
262
|
-
*
|
|
263
|
-
* it works. Migration is optional (improves going forward).
|
|
280
|
+
* Returns true if legacy dir was detected and migration was performed.
|
|
264
281
|
*/
|
|
265
282
|
export function autoApplyLegacyDir() {
|
|
266
283
|
// Already set — nothing to do
|
|
@@ -269,10 +286,39 @@ export function autoApplyLegacyDir() {
|
|
|
269
286
|
const legacyDir = getLegacyConfigDir();
|
|
270
287
|
if (!legacyDir) return false;
|
|
271
288
|
|
|
272
|
-
|
|
289
|
+
const newDir = getNewConfigDir();
|
|
290
|
+
|
|
291
|
+
// If new dir already has config, just use it (already migrated)
|
|
292
|
+
if (hasLegacyMarkers(newDir)) {
|
|
293
|
+
// Legacy dir still exists but new dir is set up — clean up legacy
|
|
294
|
+
scheduleLegacyCleanup(legacyDir);
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Perform migration
|
|
273
299
|
console.log(
|
|
274
|
-
`[compat] Legacy codex-monitor config detected at ${legacyDir} —
|
|
300
|
+
`[compat] Legacy codex-monitor config detected at ${legacyDir} — migrating to ${newDir}...`,
|
|
275
301
|
);
|
|
302
|
+
const result = migrateFromLegacy(legacyDir, newDir);
|
|
303
|
+
|
|
304
|
+
if (result.errors.length > 0) {
|
|
305
|
+
console.warn(
|
|
306
|
+
`[compat] Migration had errors: ${result.errors.join(", ")}`,
|
|
307
|
+
);
|
|
308
|
+
// Fall back to legacy dir if migration failed
|
|
309
|
+
process.env.BOSUN_DIR = legacyDir;
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (result.migrated.length > 0) {
|
|
314
|
+
console.log(
|
|
315
|
+
`[compat] Migrated ${result.migrated.length} files to ${newDir}: ${result.migrated.join(", ")}`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Schedule cleanup of legacy directory
|
|
320
|
+
scheduleLegacyCleanup(legacyDir);
|
|
321
|
+
|
|
276
322
|
return true;
|
|
277
323
|
}
|
|
278
324
|
|
package/config.mjs
CHANGED
|
@@ -59,11 +59,6 @@ function isWslInteropRuntime() {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function resolveConfigDir(repoRoot) {
|
|
62
|
-
const repoPath = resolve(repoRoot || process.cwd());
|
|
63
|
-
const packageDir = resolve(__dirname);
|
|
64
|
-
if (isPathInside(repoPath, packageDir) || hasConfigFiles(packageDir)) {
|
|
65
|
-
return packageDir;
|
|
66
|
-
}
|
|
67
62
|
const preferWindowsDirs =
|
|
68
63
|
process.platform === "win32" && !isWslInteropRuntime();
|
|
69
64
|
const baseDir = preferWindowsDirs
|
|
@@ -737,6 +732,15 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
737
732
|
let configData = configFile.data || {};
|
|
738
733
|
|
|
739
734
|
const repoRootOverride = cli["repo-root"] || process.env.REPO_ROOT || "";
|
|
735
|
+
|
|
736
|
+
// Load workspace configuration
|
|
737
|
+
const workspacesDir = resolve(configDir, "workspaces");
|
|
738
|
+
const activeWorkspace = cli["workspace"] ||
|
|
739
|
+
process.env.BOSUN_WORKSPACE ||
|
|
740
|
+
configData.activeWorkspace ||
|
|
741
|
+
configData.defaultWorkspace ||
|
|
742
|
+
"";
|
|
743
|
+
|
|
740
744
|
let repositories = loadRepoConfig(configDir, configData, {
|
|
741
745
|
repoRootOverride,
|
|
742
746
|
});
|
|
@@ -1626,9 +1630,11 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1626
1630
|
executorConfig,
|
|
1627
1631
|
scheduler,
|
|
1628
1632
|
|
|
1629
|
-
// Multi-repo
|
|
1633
|
+
// Multi-repo / Workspaces
|
|
1630
1634
|
repositories,
|
|
1631
1635
|
selectedRepository,
|
|
1636
|
+
workspacesDir,
|
|
1637
|
+
activeWorkspace,
|
|
1632
1638
|
|
|
1633
1639
|
// Agent prompts
|
|
1634
1640
|
agentPrompts,
|
package/monitor.mjs
CHANGED
|
@@ -6315,7 +6315,7 @@ function resolveUpstreamFromTask(task) {
|
|
|
6315
6315
|
if (
|
|
6316
6316
|
text.includes("bosun") ||
|
|
6317
6317
|
text.includes("codex monitor") ||
|
|
6318
|
-
text.includes("
|
|
6318
|
+
text.includes("bosun") ||
|
|
6319
6319
|
text.includes("scripts/bosun")
|
|
6320
6320
|
) {
|
|
6321
6321
|
return DEFAULT_BOSUN_UPSTREAM;
|
package/package.json
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.1",
|
|
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",
|
|
7
|
-
"author": "VirtEngine <
|
|
8
|
-
"homepage": "https://
|
|
7
|
+
"author": "VirtEngine Maintainers <maintainers@virtengine.com>",
|
|
8
|
+
"homepage": "https://bosun.virtengine.com",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/virtengine/
|
|
12
|
-
"directory": "scripts/bosun"
|
|
11
|
+
"url": "git+https://github.com/virtengine/bosun.git"
|
|
13
12
|
},
|
|
14
13
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/virtengine/
|
|
14
|
+
"url": "https://github.com/virtengine/bosun/issues"
|
|
16
15
|
},
|
|
17
16
|
"keywords": [
|
|
17
|
+
"bosun",
|
|
18
18
|
"codex",
|
|
19
19
|
"orchestrator",
|
|
20
20
|
"monitor",
|
|
21
21
|
"ai",
|
|
22
22
|
"automation",
|
|
23
|
-
"vibe-kanban",
|
|
24
23
|
"telegram",
|
|
25
24
|
"devops",
|
|
26
25
|
"executor",
|
|
@@ -44,6 +43,7 @@
|
|
|
44
43
|
"./maintenance": "./maintenance.mjs",
|
|
45
44
|
"./telegram-bot": "./telegram-bot.mjs",
|
|
46
45
|
"./ui-server": "./ui-server.mjs",
|
|
46
|
+
"./workspace-manager": "./workspace-manager.mjs",
|
|
47
47
|
"./workspace-registry": "./workspace-registry.mjs",
|
|
48
48
|
"./shared-workspace-registry": "./shared-workspace-registry.mjs",
|
|
49
49
|
"./workspace-reaper": "./workspace-reaper.mjs",
|
|
@@ -174,6 +174,7 @@
|
|
|
174
174
|
"vibe-kanban-wrapper.mjs",
|
|
175
175
|
"vk-error-resolver.mjs",
|
|
176
176
|
"vk-log-stream.mjs",
|
|
177
|
+
"workspace-manager.mjs",
|
|
177
178
|
"workspace-monitor.mjs",
|
|
178
179
|
"workspace-reaper.mjs",
|
|
179
180
|
"workspace-registry.mjs",
|
package/postinstall.mjs
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { execSync } from "node:child_process";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { resolve } from "node:path";
|
|
15
17
|
|
|
16
18
|
const isWin = process.platform === "win32";
|
|
17
19
|
|
|
@@ -182,6 +184,18 @@ function main() {
|
|
|
182
184
|
console.log(" bosun Start with existing config");
|
|
183
185
|
console.log(" bosun --help See all options");
|
|
184
186
|
console.log("");
|
|
187
|
+
|
|
188
|
+
// Auto-install git hooks when inside the repo and hooks are present.
|
|
189
|
+
try {
|
|
190
|
+
if (process.env.BOSUN_SKIP_GIT_HOOKS) return;
|
|
191
|
+
const cwd = process.cwd();
|
|
192
|
+
const hooksDir = resolve(cwd, ".githooks");
|
|
193
|
+
if (existsSync(resolve(cwd, ".git")) && existsSync(hooksDir)) {
|
|
194
|
+
execSync("git config core.hooksPath .githooks", { stdio: "ignore" });
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
// Non-blocking; hooks can be installed via `npm run hooks:install`
|
|
198
|
+
}
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
main();
|
package/publish.mjs
CHANGED
|
@@ -224,10 +224,8 @@ function main() {
|
|
|
224
224
|
const status = run(NPM_BIN, publishArgs, env);
|
|
225
225
|
if (status === 0 && !dryRun) {
|
|
226
226
|
console.log(
|
|
227
|
-
"\n[publish]
|
|
228
|
-
" npm deprecate
|
|
229
|
-
" # If a scoped legacy package exists:\n" +
|
|
230
|
-
" npm deprecate @virtengine/bosun@'*' \"Renamed to @virtengine/bosun. Install: npm install -g @virtengine/bosun\"\n",
|
|
227
|
+
"\n[publish] :\n" +
|
|
228
|
+
" npm deprecate openfleet@'*' \"⚠️ openfleet has been renamed to bosun. Install the latest: npm install -g bosun\"\n",
|
|
231
229
|
);
|
|
232
230
|
}
|
|
233
231
|
process.exit(status);
|
package/session-tracker.mjs
CHANGED
|
@@ -506,6 +506,19 @@ export class SessionTracker {
|
|
|
506
506
|
this.#markDirty(sessionId);
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Rename a session (update its title).
|
|
511
|
+
* @param {string} sessionId
|
|
512
|
+
* @param {string} newTitle
|
|
513
|
+
*/
|
|
514
|
+
renameSession(sessionId, newTitle) {
|
|
515
|
+
const session = this.#sessions.get(sessionId);
|
|
516
|
+
if (!session) return;
|
|
517
|
+
session.taskTitle = newTitle;
|
|
518
|
+
session.title = newTitle;
|
|
519
|
+
this.#markDirty(sessionId);
|
|
520
|
+
}
|
|
521
|
+
|
|
509
522
|
/**
|
|
510
523
|
* Flush all dirty sessions to disk immediately.
|
|
511
524
|
*/
|