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.
Files changed (45) hide show
  1. package/README.md +11 -8
  2. package/agent-hook-bridge.mjs +6 -3
  3. package/agent-hooks.mjs +9 -0
  4. package/agent-prompts.mjs +26 -0
  5. package/autofix.mjs +30 -6
  6. package/bosun.schema.json +47 -0
  7. package/cli.mjs +98 -1
  8. package/compat.mjs +54 -8
  9. package/config.mjs +12 -6
  10. package/monitor.mjs +1 -1
  11. package/package.json +8 -7
  12. package/postinstall.mjs +14 -0
  13. package/publish.mjs +2 -4
  14. package/session-tracker.mjs +13 -0
  15. package/setup.mjs +124 -46
  16. package/startup-service.mjs +1 -1
  17. package/task-executor.mjs +50 -3
  18. package/task-store.mjs +3 -0
  19. package/telegram-bot.mjs +49 -2
  20. package/ui/app.js +157 -25
  21. package/ui/components/kanban-board.js +56 -9
  22. package/ui/components/session-list.js +105 -93
  23. package/ui/components/workspace-switcher.js +121 -0
  24. package/ui/demo.html +691 -41
  25. package/ui/index.html +11 -3
  26. package/ui/modules/icons.js +34 -0
  27. package/ui/modules/router.js +9 -4
  28. package/ui/styles/base.css +18 -5
  29. package/ui/styles/components.css +1369 -287
  30. package/ui/styles/kanban.css +39 -1
  31. package/ui/styles/layout.css +260 -73
  32. package/ui/styles/sessions.css +219 -3
  33. package/ui/styles/variables.css +124 -52
  34. package/ui/styles/workspace-switcher.css +142 -0
  35. package/ui/tabs/agents.js +326 -181
  36. package/ui/tabs/chat.js +352 -12
  37. package/ui/tabs/control.js +438 -387
  38. package/ui/tabs/dashboard.js +356 -156
  39. package/ui/tabs/settings.js +93 -16
  40. package/ui/tabs/tasks.js +368 -132
  41. package/ui-server.mjs +183 -1
  42. package/update-check.mjs +18 -6
  43. package/ve-orchestrator.ps1 +1 -1
  44. package/vibe-kanban-wrapper.mjs +1 -1
  45. package/workspace-manager.mjs +527 -0
package/README.md CHANGED
@@ -1,19 +1,19 @@
1
- # @virtengine/bosun
1
+ # bosun
2
2
 
3
- **Bosun** is a production-grade supervisor for AI coding agents. It routes tasks across executors, manages retries and failover, automates PR lifecycle, and keeps you in control through Telegram (with optional WhatsApp and container isolation).
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/virtengine/tree/main/scripts/bosun) · [npm](https://www.npmjs.com/package/@virtengine/bosun) · [Issues](https://github.com/virtengine/virtengine/issues)
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
- ![CI](https://github.com/virtengine/virtengine/actions/workflows/ci.yaml/badge.svg)
8
- ![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)
9
- ![npm](https://img.shields.io/npm/v/@virtengine/bosun.svg)
7
+ [![CI](https://github.com/virtengine/bosun/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/virtengine/bosun/actions/workflows/ci.yaml)
8
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
9
+ [![npm](https://img.shields.io/npm/v/bosun.svg)](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 @virtengine/bosun
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. Keep `site/docs` in sync with these markdown files so the website mirrors the same content.
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
  ---
@@ -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: process.env.VE_TASK_TITLE || "",
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: process.env.VE_BRANCH_NAME || "",
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 monorepo markers (source repo)
352
- const repoRoot = resolve(__dirname, "..", "..");
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
- const isMonoRepo = monoRepoMarkers.some((m) =>
355
- existsSync(resolve(repoRoot, m)),
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/@virtengine/bosun
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
- * If BOSUN_DIR is not set but CODEX_MONITOR_DIR is (or legacy dir exists),
259
- * transparently set BOSUN_DIR to point at the legacy dir so bosun reads
260
- * from it without requiring a migration step.
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
- * This is the "zero-friction" path: existing users just upgrade the package and
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
- process.env.BOSUN_DIR = legacyDir;
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} — using it as BOSUN_DIR.`,
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("@virtengine/bosun") ||
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.26.6",
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 <dev@virtengine.com>",
8
- "homepage": "https://github.com/virtengine/bosun",
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/virtengine.git",
12
- "directory": "scripts/bosun"
11
+ "url": "git+https://github.com/virtengine/bosun.git"
13
12
  },
14
13
  "bugs": {
15
- "url": "https://github.com/virtengine/virtengine/issues"
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] REMINDER: deprecate the legacy npm package to redirect users:\n" +
228
- " npm deprecate bosun@'*' \"Renamed to @virtengine/bosun. Install: npm install -g @virtengine/bosun\"\n" +
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);
@@ -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
  */