bosun 0.34.6 → 0.34.7
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/agent-hook-bridge.mjs +6 -6
- package/agent-pool.mjs +75 -5
- package/bosun.schema.json +31 -0
- package/cli.mjs +18 -3
- package/codex-config.mjs +15 -9
- package/copilot-shell.mjs +75 -5
- package/desktop/main.mjs +3 -0
- package/git-commit-helpers.mjs +31 -3
- package/library-manager.mjs +6 -4
- package/monitor.mjs +26 -9
- package/package.json +2 -1
- package/postinstall.mjs +8 -5
- package/primary-agent.mjs +16 -1
- package/repo-config.mjs +72 -4
- package/setup-web-server.mjs +4 -3
- package/setup.mjs +49 -57
- package/task-context.mjs +200 -0
- package/task-executor.mjs +50 -16
- package/telegram-bot.mjs +13 -4
- package/ui/app.js +1 -1
- package/ui/components/agent-selector.js +6 -1
- package/ui/components/chat-view.js +2 -2
- package/ui/components/diff-viewer.js +1 -1
- package/ui/components/workspace-switcher.js +4 -4
- package/ui/modules/agent-display.js +5 -5
- package/ui/styles/base.css +1 -1
- package/ui/styles/workspace-switcher.css +10 -0
- package/ui/tabs/agents.js +6 -6
- package/ui/tabs/chat.js +33 -33
- package/ui/tabs/dashboard.js +10 -10
- package/ui/tabs/library.js +18 -16
- package/ui/tabs/settings.js +161 -15
- package/ui/tabs/tasks.js +8 -8
- package/ui/tabs/workflows.js +3 -2
- package/ui-server.mjs +131 -7
- package/workflow-nodes.mjs +361 -1
package/agent-hook-bridge.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { executeBlockingHooks, executeHooks, loadHooks, registerBuiltinHooks } from "./agent-hooks.mjs";
|
|
5
|
+
import { shouldRunAgentHookBridge } from "./task-context.mjs";
|
|
5
6
|
|
|
6
7
|
const TAG = "[agent-hook-bridge]";
|
|
7
8
|
|
|
@@ -161,12 +162,11 @@ function mapEvents(sourceEvent, payload) {
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
async function run() {
|
|
164
|
-
// ──
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
if (!process.env.VE_MANAGED && !process.env.BOSUN_HOOKS_FORCE) {
|
|
165
|
+
// ── task-context guard ────────────────────────────────────────────────────
|
|
166
|
+
// Execute hooks only for active Bosun task sessions.
|
|
167
|
+
// This prevents globally scaffolded hook configs from affecting standalone
|
|
168
|
+
// sessions in user repos. BOSUN_HOOKS_FORCE remains an explicit override.
|
|
169
|
+
if (!shouldRunAgentHookBridge(process.env)) {
|
|
170
170
|
process.exit(0);
|
|
171
171
|
}
|
|
172
172
|
|
package/agent-pool.mjs
CHANGED
|
@@ -45,6 +45,16 @@ import { loadConfig } from "./config.mjs";
|
|
|
45
45
|
import { resolveRepoRoot, resolveAgentRepoRoot } from "./repo-root.mjs";
|
|
46
46
|
import { resolveCodexProfileRuntime } from "./codex-model-profiles.mjs";
|
|
47
47
|
|
|
48
|
+
// Lazy-load MCP registry to avoid circular dependencies.
|
|
49
|
+
// Cached at module scope per AGENTS.md hard rules.
|
|
50
|
+
let _mcpRegistry = null;
|
|
51
|
+
async function getMcpRegistry() {
|
|
52
|
+
if (!_mcpRegistry) {
|
|
53
|
+
_mcpRegistry = await import("./mcp-registry.mjs");
|
|
54
|
+
}
|
|
55
|
+
return _mcpRegistry;
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
const __filename = fileURLToPath(import.meta.url);
|
|
49
59
|
const __dirname = dirname(__filename);
|
|
50
60
|
|
|
@@ -897,7 +907,7 @@ async function launchCodexThread(prompt, cwd, timeoutMs, extra = {}) {
|
|
|
897
907
|
* Build CLI arguments for ephemeral Copilot agent-pool sessions.
|
|
898
908
|
* Mirrors copilot-shell.mjs buildCliArgs() for feature parity.
|
|
899
909
|
*/
|
|
900
|
-
function buildPoolCopilotCliArgs() {
|
|
910
|
+
function buildPoolCopilotCliArgs(mcpConfigPath) {
|
|
901
911
|
const args = [];
|
|
902
912
|
if (!envFlagEnabled(process.env.COPILOT_NO_EXPERIMENTAL)) {
|
|
903
913
|
args.push("--experimental");
|
|
@@ -915,9 +925,10 @@ function buildPoolCopilotCliArgs() {
|
|
|
915
925
|
if (envFlagEnabled(process.env.COPILOT_DISABLE_BUILTIN_MCPS)) {
|
|
916
926
|
args.push("--disable-builtin-mcps");
|
|
917
927
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
928
|
+
// MCP config: prefer per-launch resolved config, fall back to env var
|
|
929
|
+
const effectiveMcpConfig = mcpConfigPath || process.env.COPILOT_ADDITIONAL_MCP_CONFIG;
|
|
930
|
+
if (effectiveMcpConfig) {
|
|
931
|
+
args.push("--additional-mcp-config", effectiveMcpConfig);
|
|
921
932
|
}
|
|
922
933
|
return args;
|
|
923
934
|
}
|
|
@@ -1024,7 +1035,17 @@ async function launchCopilotThread(prompt, cwd, timeoutMs, extra = {}) {
|
|
|
1024
1035
|
clientOpts = { cliUrl, env: clientEnv };
|
|
1025
1036
|
} else {
|
|
1026
1037
|
// Local mode (default): stdio for full capability
|
|
1027
|
-
|
|
1038
|
+
// Write temp MCP config if resolved MCP servers are available
|
|
1039
|
+
let mcpConfigPath = null;
|
|
1040
|
+
if (extra._resolvedMcpServers?.length) {
|
|
1041
|
+
try {
|
|
1042
|
+
const registry = await getMcpRegistry();
|
|
1043
|
+
mcpConfigPath = registry.writeTempCopilotMcpConfig(cwd, extra._resolvedMcpServers);
|
|
1044
|
+
} catch (mcpErr) {
|
|
1045
|
+
console.warn(`${TAG} copilot MCP config write failed (non-fatal): ${mcpErr.message}`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
const cliArgs = buildPoolCopilotCliArgs(mcpConfigPath);
|
|
1028
1049
|
clientOpts = {
|
|
1029
1050
|
cwd,
|
|
1030
1051
|
env: clientEnv,
|
|
@@ -1465,6 +1486,27 @@ async function launchClaudeThread(prompt, cwd, timeoutMs, extra = {}) {
|
|
|
1465
1486
|
}
|
|
1466
1487
|
|
|
1467
1488
|
// ── 4. Execute query ─────────────────────────────────────────────────────
|
|
1489
|
+
// Inject MCP server config for Claude via environment variable
|
|
1490
|
+
let _claudeMcpEnvCleanup = null;
|
|
1491
|
+
if (extra._resolvedMcpServers?.length) {
|
|
1492
|
+
try {
|
|
1493
|
+
const registry = await getMcpRegistry();
|
|
1494
|
+
const { envVar } = registry.buildClaudeMcpEnv(extra._resolvedMcpServers);
|
|
1495
|
+
if (envVar) {
|
|
1496
|
+
const prev = process.env.CLAUDE_MCP_SERVERS;
|
|
1497
|
+
process.env.CLAUDE_MCP_SERVERS = envVar;
|
|
1498
|
+
_claudeMcpEnvCleanup = () => {
|
|
1499
|
+
if (prev === undefined) {
|
|
1500
|
+
delete process.env.CLAUDE_MCP_SERVERS;
|
|
1501
|
+
} else {
|
|
1502
|
+
process.env.CLAUDE_MCP_SERVERS = prev;
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
} catch (mcpErr) {
|
|
1507
|
+
console.warn(`${TAG} claude MCP env setup failed (non-fatal): ${mcpErr.message}`);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1468
1510
|
try {
|
|
1469
1511
|
const msgQueue = createMessageQueue();
|
|
1470
1512
|
|
|
@@ -1596,6 +1638,7 @@ async function launchClaudeThread(prompt, cwd, timeoutMs, extra = {}) {
|
|
|
1596
1638
|
|
|
1597
1639
|
const output =
|
|
1598
1640
|
finalResponse.trim() || "(Agent completed with no text output)";
|
|
1641
|
+
if (typeof _claudeMcpEnvCleanup === "function") _claudeMcpEnvCleanup();
|
|
1599
1642
|
return {
|
|
1600
1643
|
success: true,
|
|
1601
1644
|
output,
|
|
@@ -1605,6 +1648,7 @@ async function launchClaudeThread(prompt, cwd, timeoutMs, extra = {}) {
|
|
|
1605
1648
|
threadId: activeClaudeSessionId,
|
|
1606
1649
|
};
|
|
1607
1650
|
} catch (err) {
|
|
1651
|
+
if (typeof _claudeMcpEnvCleanup === "function") _claudeMcpEnvCleanup();
|
|
1608
1652
|
clearAbortScope();
|
|
1609
1653
|
if (hardTimer) clearTimeout(hardTimer);
|
|
1610
1654
|
if (steerKey) unregisterActiveSession(steerKey);
|
|
@@ -1714,6 +1758,32 @@ export async function launchEphemeralThread(
|
|
|
1714
1758
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
1715
1759
|
extra = {},
|
|
1716
1760
|
) {
|
|
1761
|
+
// ── Resolve MCP servers for this launch ──────────────────────────────────
|
|
1762
|
+
try {
|
|
1763
|
+
if (!extra._mcpResolved) {
|
|
1764
|
+
const cfg = loadConfig();
|
|
1765
|
+
const mcpCfg = cfg.mcpServers || {};
|
|
1766
|
+
if (mcpCfg.enabled !== false) {
|
|
1767
|
+
const requestedIds = extra.mcpServers || [];
|
|
1768
|
+
const defaultIds = mcpCfg.defaultServers || [];
|
|
1769
|
+
if (requestedIds.length || defaultIds.length) {
|
|
1770
|
+
const registry = await getMcpRegistry();
|
|
1771
|
+
const resolved = await registry.resolveMcpServersForAgent(
|
|
1772
|
+
cwd,
|
|
1773
|
+
requestedIds,
|
|
1774
|
+
{ defaultServers: defaultIds, catalogOverrides: mcpCfg.catalogOverrides || {} },
|
|
1775
|
+
);
|
|
1776
|
+
if (resolved.length) {
|
|
1777
|
+
extra._resolvedMcpServers = resolved;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
extra._mcpResolved = true;
|
|
1782
|
+
}
|
|
1783
|
+
} catch (mcpErr) {
|
|
1784
|
+
console.warn(`${TAG} MCP server resolution failed (non-fatal): ${mcpErr.message}`);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1717
1787
|
// Determine the primary SDK to try
|
|
1718
1788
|
const requestedSdk = extra.sdk
|
|
1719
1789
|
? String(extra.sdk).trim().toLowerCase()
|
package/bosun.schema.json
CHANGED
|
@@ -606,6 +606,37 @@
|
|
|
606
606
|
"type": "object",
|
|
607
607
|
"description": "Hook definitions loaded by agent-hooks.mjs (event -> hook list).",
|
|
608
608
|
"additionalProperties": true
|
|
609
|
+
},
|
|
610
|
+
"mcpServers": {
|
|
611
|
+
"type": "object",
|
|
612
|
+
"description": "MCP (Model Context Protocol) server management. Controls which MCP servers are available to agents.",
|
|
613
|
+
"additionalProperties": false,
|
|
614
|
+
"properties": {
|
|
615
|
+
"enabled": {
|
|
616
|
+
"type": "boolean",
|
|
617
|
+
"default": true,
|
|
618
|
+
"description": "Enable MCP server integration for agent launches"
|
|
619
|
+
},
|
|
620
|
+
"defaultServers": {
|
|
621
|
+
"type": "array",
|
|
622
|
+
"items": { "type": "string" },
|
|
623
|
+
"description": "MCP server IDs attached to every agent launch by default (e.g. [\"context7\", \"microsoft-docs\"])"
|
|
624
|
+
},
|
|
625
|
+
"catalogOverrides": {
|
|
626
|
+
"type": "object",
|
|
627
|
+
"additionalProperties": {
|
|
628
|
+
"type": "object",
|
|
629
|
+
"description": "Per-server environment variable overrides",
|
|
630
|
+
"additionalProperties": { "type": "string" }
|
|
631
|
+
},
|
|
632
|
+
"description": "Environment variable overrides keyed by MCP server ID"
|
|
633
|
+
},
|
|
634
|
+
"autoInstallDefaults": {
|
|
635
|
+
"type": "boolean",
|
|
636
|
+
"default": true,
|
|
637
|
+
"description": "Automatically install defaultServers from catalog if not already installed"
|
|
638
|
+
}
|
|
639
|
+
}
|
|
609
640
|
}
|
|
610
641
|
}
|
|
611
642
|
}
|
package/cli.mjs
CHANGED
|
@@ -1484,14 +1484,21 @@ function detectExistingMonitorLockOwner(excludePid = null) {
|
|
|
1484
1484
|
return null;
|
|
1485
1485
|
}
|
|
1486
1486
|
|
|
1487
|
-
function runMonitor() {
|
|
1487
|
+
function runMonitor({ restartReason = "" } = {}) {
|
|
1488
1488
|
return new Promise((resolve, reject) => {
|
|
1489
1489
|
const monitorPath = fileURLToPath(
|
|
1490
1490
|
new URL("./monitor.mjs", import.meta.url),
|
|
1491
1491
|
);
|
|
1492
|
+
const childEnv = { ...process.env };
|
|
1493
|
+
if (restartReason) {
|
|
1494
|
+
childEnv.BOSUN_MONITOR_RESTART_REASON = restartReason;
|
|
1495
|
+
} else {
|
|
1496
|
+
delete childEnv.BOSUN_MONITOR_RESTART_REASON;
|
|
1497
|
+
}
|
|
1492
1498
|
monitorChild = fork(monitorPath, process.argv.slice(2), {
|
|
1493
1499
|
stdio: "inherit",
|
|
1494
1500
|
execArgv: ["--max-old-space-size=4096"],
|
|
1501
|
+
env: childEnv,
|
|
1495
1502
|
windowsHide: IS_DAEMON_CHILD && process.platform === "win32",
|
|
1496
1503
|
});
|
|
1497
1504
|
daemonCrashTracker.markStart();
|
|
@@ -1504,7 +1511,7 @@ function runMonitor() {
|
|
|
1504
1511
|
"\n \u21BB Monitor restarting with fresh modules...\n",
|
|
1505
1512
|
);
|
|
1506
1513
|
// Small delay to let file writes / port releases settle
|
|
1507
|
-
setTimeout(() => resolve(runMonitor()), 2000);
|
|
1514
|
+
setTimeout(() => resolve(runMonitor({ restartReason: "self-restart" })), 2000);
|
|
1508
1515
|
} else {
|
|
1509
1516
|
const exitCode = code ?? (signal ? 1 : 0);
|
|
1510
1517
|
const existingOwner =
|
|
@@ -1572,7 +1579,15 @@ function runMonitor() {
|
|
|
1572
1579
|
restartAttempt: daemonRestartCount,
|
|
1573
1580
|
maxRestarts: IS_DAEMON_CHILD ? DAEMON_MAX_RESTARTS : 0,
|
|
1574
1581
|
}).catch(() => {});
|
|
1575
|
-
setTimeout(
|
|
1582
|
+
setTimeout(
|
|
1583
|
+
() =>
|
|
1584
|
+
resolve(
|
|
1585
|
+
runMonitor({
|
|
1586
|
+
restartReason: isOSKill ? "os-kill" : "crash",
|
|
1587
|
+
}),
|
|
1588
|
+
),
|
|
1589
|
+
delayMs,
|
|
1590
|
+
);
|
|
1576
1591
|
return;
|
|
1577
1592
|
}
|
|
1578
1593
|
|
package/codex-config.mjs
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* codex-config.mjs — Manages the Codex CLI config (~/.codex/config.toml)
|
|
3
3
|
*
|
|
4
4
|
* Ensures the user's Codex CLI configuration has:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
10
|
-
*
|
|
5
|
+
* 1. Sufficient stream_idle_timeout_ms on all model providers
|
|
6
|
+
* 2. Recommended defaults for long-running agentic workloads
|
|
7
|
+
* 3. Feature flags for sub-agents, memory, undo, collaboration
|
|
8
|
+
* 4. Sandbox permissions and shell environment policy
|
|
9
|
+
* 5. Common MCP servers (context7, microsoft-docs)
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Vibe-Kanban MCP is workspace-scoped and managed by repo-config.mjs
|
|
12
|
+
* inside each repo's `.codex/config.toml`. Global config no longer auto-adds
|
|
13
|
+
* `[mcp_servers.vibe_kanban]`.
|
|
11
14
|
*
|
|
12
15
|
* SCOPE: This manages the GLOBAL ~/.codex/config.toml which contains:
|
|
13
16
|
* - Model provider configs (API keys, base URLs) — MUST be global
|
|
@@ -1305,13 +1308,15 @@ export function ensureRetrySettings(toml, providerName) {
|
|
|
1305
1308
|
* @param {object} opts
|
|
1306
1309
|
* @param {string} [opts.vkBaseUrl]
|
|
1307
1310
|
* @param {boolean} [opts.skipVk]
|
|
1311
|
+
* @param {boolean} [opts.manageVkMcp] Explicit opt-in to manage VK MCP in global config
|
|
1308
1312
|
* @param {boolean} [opts.dryRun] If true, returns result without writing
|
|
1309
1313
|
* @param {object} [opts.env] Environment overrides (defaults to process.env)
|
|
1310
1314
|
* @param {string} [opts.primarySdk] Primary agent SDK: "codex", "copilot", or "claude"
|
|
1311
1315
|
*/
|
|
1312
1316
|
export function ensureCodexConfig({
|
|
1313
1317
|
vkBaseUrl = "http://127.0.0.1:54089",
|
|
1314
|
-
skipVk =
|
|
1318
|
+
skipVk = true,
|
|
1319
|
+
manageVkMcp = false,
|
|
1315
1320
|
dryRun = false,
|
|
1316
1321
|
env = process.env,
|
|
1317
1322
|
primarySdk,
|
|
@@ -1424,7 +1429,8 @@ export function ensureCodexConfig({
|
|
|
1424
1429
|
result.featuresAdded = featureResult.added;
|
|
1425
1430
|
toml = featureResult.toml;
|
|
1426
1431
|
|
|
1427
|
-
|
|
1432
|
+
const shouldManageGlobalVkMcp = Boolean(manageVkMcp) && !skipVk;
|
|
1433
|
+
if (!shouldManageGlobalVkMcp) {
|
|
1428
1434
|
if (hasVibeKanbanMcp(toml)) {
|
|
1429
1435
|
toml = removeVibeKanbanMcp(toml);
|
|
1430
1436
|
result.vkRemoved = true;
|
|
@@ -1564,7 +1570,7 @@ export function printConfigSummary(result, log = console.log) {
|
|
|
1564
1570
|
}
|
|
1565
1571
|
|
|
1566
1572
|
if (result.vkRemoved) {
|
|
1567
|
-
log(" 🗑️ Removed Vibe-Kanban MCP server
|
|
1573
|
+
log(" 🗑️ Removed Vibe-Kanban MCP server from global config (workspace-scoped only)");
|
|
1568
1574
|
}
|
|
1569
1575
|
|
|
1570
1576
|
if (result.vkEnvUpdated) {
|
package/copilot-shell.mjs
CHANGED
|
@@ -17,6 +17,15 @@ import { getGitHubToken } from "./github-auth-manager.mjs";
|
|
|
17
17
|
|
|
18
18
|
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
19
19
|
|
|
20
|
+
// Lazy-import MCP registry — cached at module scope per AGENTS.md rules.
|
|
21
|
+
let _mcpRegistry = null;
|
|
22
|
+
async function getMcpRegistry() {
|
|
23
|
+
if (!_mcpRegistry) {
|
|
24
|
+
_mcpRegistry = await import("./mcp-registry.mjs");
|
|
25
|
+
}
|
|
26
|
+
return _mcpRegistry;
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
// ── Configuration ────────────────────────────────────────────────────────────
|
|
21
30
|
|
|
22
31
|
const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000; // 60 min for agentic tasks
|
|
@@ -174,7 +183,7 @@ function logSessionEvent(logPath, event) {
|
|
|
174
183
|
* Enables experimental features (fleet, autopilot), auto-permissions,
|
|
175
184
|
* sub-agents, and autonomy.
|
|
176
185
|
*/
|
|
177
|
-
function buildCliArgs() {
|
|
186
|
+
async function buildCliArgs() {
|
|
178
187
|
const args = [];
|
|
179
188
|
|
|
180
189
|
// Always enable experimental features (fleet, autopilot, persisted permissions, etc.)
|
|
@@ -218,6 +227,26 @@ function buildCliArgs() {
|
|
|
218
227
|
args.push("--additional-mcp-config", mcpConfigPath);
|
|
219
228
|
}
|
|
220
229
|
|
|
230
|
+
// Also write a temp MCP config from the library if installed servers exist
|
|
231
|
+
// (non-fatal: library MCP is a convenience, not a hard requirement)
|
|
232
|
+
if (!mcpConfigPath) {
|
|
233
|
+
try {
|
|
234
|
+
const registry = await getMcpRegistry();
|
|
235
|
+
const installed = await registry.listInstalledMcpServers(REPO_ROOT);
|
|
236
|
+
if (installed && installed.length) {
|
|
237
|
+
const ids = installed.map((e) => e.id);
|
|
238
|
+
const resolved = await registry.resolveMcpServersForAgent(REPO_ROOT, ids);
|
|
239
|
+
if (resolved && resolved.length) {
|
|
240
|
+
const tmpPath = registry.writeTempCopilotMcpConfig(REPO_ROOT, resolved);
|
|
241
|
+
args.push("--additional-mcp-config", tmpPath);
|
|
242
|
+
console.log(`[copilot-shell] injected ${resolved.length} library MCP server(s) via CLI args`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.warn(`[copilot-shell] failed to inject library MCP servers into CLI args: ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
221
250
|
if (args.length > 0) {
|
|
222
251
|
console.log(`[copilot-shell] cliArgs: ${args.join(" ")}`);
|
|
223
252
|
}
|
|
@@ -399,7 +428,7 @@ async function ensureClientStarted() {
|
|
|
399
428
|
const sessionMode = (process.env.COPILOT_SESSION_MODE || "local").trim().toLowerCase();
|
|
400
429
|
|
|
401
430
|
// Build cliArgs for experimental features, permissions, and autonomy
|
|
402
|
-
const cliArgs = buildCliArgs();
|
|
431
|
+
const cliArgs = await buildCliArgs();
|
|
403
432
|
|
|
404
433
|
let clientOptions;
|
|
405
434
|
if (transport === "url") {
|
|
@@ -553,7 +582,46 @@ function loadMcpServers(profile = null) {
|
|
|
553
582
|
return loadMcpServersFromFile(configPath);
|
|
554
583
|
}
|
|
555
584
|
|
|
556
|
-
|
|
585
|
+
/**
|
|
586
|
+
* Merge installed MCP library servers into an existing mcpServers map.
|
|
587
|
+
* Called during session build to inject library-managed MCP servers into
|
|
588
|
+
* the Copilot SDK session alongside any profile/env servers.
|
|
589
|
+
*
|
|
590
|
+
* Non-fatal: if the registry is unavailable or encounters errors, the
|
|
591
|
+
* original servers map is returned unchanged.
|
|
592
|
+
*
|
|
593
|
+
* @param {Object|null} existingServers — mcpServers from profile/env/config
|
|
594
|
+
* @returns {Promise<Object|null>} — merged servers map
|
|
595
|
+
*/
|
|
596
|
+
async function mergeLibraryMcpServers(existingServers) {
|
|
597
|
+
try {
|
|
598
|
+
const registry = await getMcpRegistry();
|
|
599
|
+
const installed = await registry.listInstalledMcpServers(REPO_ROOT);
|
|
600
|
+
if (!installed || !installed.length) return existingServers;
|
|
601
|
+
|
|
602
|
+
// Resolve all installed servers into full configs
|
|
603
|
+
const installedIds = installed.map((e) => e.id);
|
|
604
|
+
const resolved = await registry.resolveMcpServersForAgent(REPO_ROOT, installedIds);
|
|
605
|
+
if (!resolved || !resolved.length) return existingServers;
|
|
606
|
+
|
|
607
|
+
// Convert to Copilot mcpServers format: { [id]: { command, args, env? } | { url } }
|
|
608
|
+
const copilotJson = registry.buildCopilotMcpJson(resolved);
|
|
609
|
+
const libraryServers = copilotJson?.mcpServers || {};
|
|
610
|
+
if (!Object.keys(libraryServers).length) return existingServers;
|
|
611
|
+
|
|
612
|
+
// Merge: existing servers take precedence over library ones (user overrides win)
|
|
613
|
+
const merged = { ...libraryServers, ...(existingServers || {}) };
|
|
614
|
+
console.log(
|
|
615
|
+
`[copilot-shell] Merged ${Object.keys(libraryServers).length} library MCP server(s) into session`,
|
|
616
|
+
);
|
|
617
|
+
return merged;
|
|
618
|
+
} catch (err) {
|
|
619
|
+
console.warn(`[copilot-shell] Failed to merge library MCP servers: ${err.message}`);
|
|
620
|
+
return existingServers;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
async function buildSessionConfig() {
|
|
557
625
|
const profile = resolveCopilotProfile();
|
|
558
626
|
const config = {
|
|
559
627
|
streaming: true,
|
|
@@ -588,7 +656,9 @@ function buildSessionConfig() {
|
|
|
588
656
|
config.reasoningEffort = effort.toLowerCase();
|
|
589
657
|
}
|
|
590
658
|
|
|
591
|
-
|
|
659
|
+
// Load MCP servers from profile/env/config, then merge library-managed servers
|
|
660
|
+
const baseServers = loadMcpServers(profile);
|
|
661
|
+
const mcpServers = await mergeLibraryMcpServers(baseServers);
|
|
592
662
|
if (mcpServers) config.mcpServers = mcpServers;
|
|
593
663
|
return config;
|
|
594
664
|
}
|
|
@@ -600,7 +670,7 @@ async function getSession() {
|
|
|
600
670
|
const started = await ensureClientStarted();
|
|
601
671
|
if (!started) throw new Error("Copilot SDK not available");
|
|
602
672
|
|
|
603
|
-
const config = buildSessionConfig();
|
|
673
|
+
const config = await buildSessionConfig();
|
|
604
674
|
|
|
605
675
|
if (activeSessionId && typeof copilotClient?.resumeSession === "function") {
|
|
606
676
|
try {
|
package/desktop/main.mjs
CHANGED
|
@@ -256,6 +256,9 @@ async function startUiServer() {
|
|
|
256
256
|
if (uiServerStarted) return;
|
|
257
257
|
const api = await loadUiServerModule();
|
|
258
258
|
const server = await api.startTelegramUiServer({
|
|
259
|
+
host: "127.0.0.1",
|
|
260
|
+
publicHost: "127.0.0.1",
|
|
261
|
+
skipAutoOpen: true,
|
|
259
262
|
dependencies: {
|
|
260
263
|
configDir: resolveDesktopConfigDir(),
|
|
261
264
|
},
|
package/git-commit-helpers.mjs
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* GitHub appearance: https://github.com/apps/bosun-ve
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { shouldAddBosunCoAuthor as shouldAddBosunCoAuthorByContext } from "./task-context.mjs";
|
|
13
|
+
|
|
12
14
|
const BOSUN_BOT_TRAILER =
|
|
13
15
|
"Co-authored-by: bosun-ve[bot] <262908237+bosun-ve[bot]@users.noreply.github.com>";
|
|
14
16
|
|
|
@@ -38,17 +40,25 @@ export function appendBosunCoAuthor(message) {
|
|
|
38
40
|
* @param {string} title - commit title (first line / summary)
|
|
39
41
|
* @param {string} [body] - commit body (optional extended description)
|
|
40
42
|
* @param {Object} [opts]
|
|
41
|
-
* @param {boolean} [opts.addBosunCredit
|
|
43
|
+
* @param {boolean} [opts.addBosunCredit] - whether to append the co-author trailer
|
|
44
|
+
* @param {string} [opts.taskId] - optional task ID for task-scoped attribution mode
|
|
45
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - optional environment override
|
|
42
46
|
* @returns {string} full commit message
|
|
43
47
|
*/
|
|
44
|
-
export function buildCommitMessage(title, body = "",
|
|
48
|
+
export function buildCommitMessage(title, body = "", opts = {}) {
|
|
49
|
+
const { addBosunCredit, taskId, env } = opts;
|
|
45
50
|
const parts = [title.trimEnd()];
|
|
46
51
|
if (body && body.trim()) {
|
|
47
52
|
parts.push(""); // blank line
|
|
48
53
|
parts.push(body.trimEnd());
|
|
49
54
|
}
|
|
50
55
|
const base = parts.join("\n");
|
|
51
|
-
|
|
56
|
+
|
|
57
|
+
const withBosunCredit =
|
|
58
|
+
typeof addBosunCredit === "boolean"
|
|
59
|
+
? addBosunCredit
|
|
60
|
+
: shouldAddBosunCoAuthor({ taskId, env });
|
|
61
|
+
return withBosunCredit ? appendBosunCoAuthor(base) : base;
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
// ── PR body helpers ───────────────────────────────────────────────────────────
|
|
@@ -81,3 +91,21 @@ export function getBosunCoAuthorTrailer() {
|
|
|
81
91
|
export function getBosunPrCredit() {
|
|
82
92
|
return BOSUN_PR_CREDIT;
|
|
83
93
|
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns whether Bosun co-author credit should be applied for the current
|
|
97
|
+
* execution context.
|
|
98
|
+
*
|
|
99
|
+
* Defaults to task-scoped behavior and supports opt-in overrides via:
|
|
100
|
+
* - BOSUN_COAUTHOR_MODE=always
|
|
101
|
+
* - BOSUN_COAUTHOR_MODE=off
|
|
102
|
+
*
|
|
103
|
+
* @param {object} [options]
|
|
104
|
+
* @param {NodeJS.ProcessEnv} [options.env]
|
|
105
|
+
* @param {string} [options.taskId]
|
|
106
|
+
* @param {"task"|"always"|"off"} [options.mode]
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
export function shouldAddBosunCoAuthor(options = {}) {
|
|
110
|
+
return shouldAddBosunCoAuthorByContext(options);
|
|
111
|
+
}
|
package/library-manager.mjs
CHANGED
|
@@ -26,9 +26,10 @@ export const LIBRARY_MANIFEST = "library.json";
|
|
|
26
26
|
export const PROMPT_DIR = ".bosun/agents";
|
|
27
27
|
export const SKILL_DIR = ".bosun/skills";
|
|
28
28
|
export const PROFILE_DIR = ".bosun/profiles";
|
|
29
|
+
export const MCP_DIR = ".bosun/mcp-servers";
|
|
29
30
|
|
|
30
31
|
/** Resource types managed by the library */
|
|
31
|
-
export const RESOURCE_TYPES = Object.freeze(["prompt", "agent", "skill"]);
|
|
32
|
+
export const RESOURCE_TYPES = Object.freeze(["prompt", "agent", "skill", "mcp"]);
|
|
32
33
|
|
|
33
34
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
34
35
|
|
|
@@ -140,12 +141,13 @@ function dirForType(rootDir, type) {
|
|
|
140
141
|
case "prompt": return resolve(root, PROMPT_DIR);
|
|
141
142
|
case "skill": return resolve(root, SKILL_DIR);
|
|
142
143
|
case "agent": return resolve(root, PROFILE_DIR);
|
|
144
|
+
case "mcp": return resolve(root, MCP_DIR);
|
|
143
145
|
default: throw new Error(`Unknown library resource type: ${type}`);
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
function extForType(type) {
|
|
148
|
-
return type === "agent" ? ".json" : ".md";
|
|
150
|
+
return (type === "agent" || type === "mcp") ? ".json" : ".md";
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
/**
|
|
@@ -439,9 +441,9 @@ export function detectScopes(repoRoot, opts = {}) {
|
|
|
439
441
|
export function resolveLibraryRefs(template, rootDir, extraVars = {}) {
|
|
440
442
|
if (typeof template !== "string") return "";
|
|
441
443
|
|
|
442
|
-
// First resolve namespaced refs: {{prompt:name}}, {{agent:name}}, {{skill:name}}
|
|
444
|
+
// First resolve namespaced refs: {{prompt:name}}, {{agent:name}}, {{skill:name}}, {{mcp:name}}
|
|
443
445
|
let resolved = template.replace(
|
|
444
|
-
/\{\{\s*(prompt|agent|skill):([A-Za-z0-9_-]+)\s*\}\}/gi,
|
|
446
|
+
/\{\{\s*(prompt|agent|skill|mcp):([A-Za-z0-9_-]+)\s*\}\}/gi,
|
|
445
447
|
(_full, type, name) => {
|
|
446
448
|
const typeLower = type.toLowerCase();
|
|
447
449
|
const id = slugify(name);
|
package/monitor.mjs
CHANGED
|
@@ -98,7 +98,10 @@ import {
|
|
|
98
98
|
resetMergeStrategyDedup,
|
|
99
99
|
} from "./merge-strategy.mjs";
|
|
100
100
|
import { assessTask, quickAssess } from "./task-assessment.mjs";
|
|
101
|
-
import {
|
|
101
|
+
import {
|
|
102
|
+
getBosunCoAuthorTrailer,
|
|
103
|
+
shouldAddBosunCoAuthor,
|
|
104
|
+
} from "./git-commit-helpers.mjs";
|
|
102
105
|
import {
|
|
103
106
|
normalizeDedupKey,
|
|
104
107
|
stripAnsi,
|
|
@@ -1611,6 +1614,21 @@ try {
|
|
|
1611
1614
|
let telegramNotifierInterval = null;
|
|
1612
1615
|
let telegramNotifierTimeout = null;
|
|
1613
1616
|
let weeklyReportLastSentAt = null;
|
|
1617
|
+
const monitorRestartReason = String(
|
|
1618
|
+
process.env.BOSUN_MONITOR_RESTART_REASON || "",
|
|
1619
|
+
)
|
|
1620
|
+
.trim()
|
|
1621
|
+
.toLowerCase();
|
|
1622
|
+
|
|
1623
|
+
function getTelegramBotStartOptions() {
|
|
1624
|
+
const restartReason = isSelfRestart
|
|
1625
|
+
? "self-restart"
|
|
1626
|
+
: monitorRestartReason;
|
|
1627
|
+
return {
|
|
1628
|
+
restartReason,
|
|
1629
|
+
suppressPortalAutoOpen: restartReason.length > 0,
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1614
1632
|
let vkRecoveryLastAt = 0;
|
|
1615
1633
|
let vkNonJsonNotifiedAt = 0;
|
|
1616
1634
|
let vkNonJsonContentTypeLoggedAt = 0;
|
|
@@ -7259,15 +7277,15 @@ function extractPrNumberFromUrl(prUrl) {
|
|
|
7259
7277
|
}
|
|
7260
7278
|
|
|
7261
7279
|
function buildFlowGateMergeBody(taskTitle, taskId) {
|
|
7262
|
-
const trailer = getBosunCoAuthorTrailer();
|
|
7263
7280
|
const safeTitle = String(taskTitle || "Task").trim() || "Task";
|
|
7264
7281
|
const safeId = String(taskId || "").trim();
|
|
7265
7282
|
const lines = [
|
|
7266
7283
|
`Merged by Bosun flow gate for: ${safeTitle}`,
|
|
7267
7284
|
safeId ? `Task: ${safeId}` : "",
|
|
7268
|
-
"",
|
|
7269
|
-
trailer,
|
|
7270
7285
|
].filter(Boolean);
|
|
7286
|
+
if (shouldAddBosunCoAuthor({ taskId: safeId })) {
|
|
7287
|
+
lines.push("", getBosunCoAuthorTrailer());
|
|
7288
|
+
}
|
|
7271
7289
|
return lines.join("\n");
|
|
7272
7290
|
}
|
|
7273
7291
|
|
|
@@ -13725,7 +13743,7 @@ function applyConfig(nextConfig, options = {}) {
|
|
|
13725
13743
|
}
|
|
13726
13744
|
if (prevTelegramBotEnabled !== telegramBotEnabled) {
|
|
13727
13745
|
if (telegramBotEnabled) {
|
|
13728
|
-
void startTelegramBot();
|
|
13746
|
+
void startTelegramBot(getTelegramBotStartOptions());
|
|
13729
13747
|
} else {
|
|
13730
13748
|
stopTelegramBot();
|
|
13731
13749
|
}
|
|
@@ -14139,17 +14157,16 @@ if (!isMonitorTestRuntime) {
|
|
|
14139
14157
|
process.exit(0);
|
|
14140
14158
|
}
|
|
14141
14159
|
|
|
14142
|
-
// ── Codex CLI config.toml: ensure
|
|
14160
|
+
// ── Codex CLI config.toml: ensure global defaults + stream timeouts ─────────
|
|
14143
14161
|
try {
|
|
14144
14162
|
const vkPort = config.vkRecoveryPort || "54089";
|
|
14145
14163
|
const vkBaseUrl = config.vkEndpointUrl || `http://127.0.0.1:${vkPort}`;
|
|
14146
|
-
const skipVk = !isVkRuntimeRequired();
|
|
14147
14164
|
const allowRuntimeCodexMutation = isTruthyFlag(
|
|
14148
14165
|
process.env.BOSUN_ALLOW_RUNTIME_GLOBAL_CODEX_MUTATION,
|
|
14149
14166
|
);
|
|
14150
14167
|
const tomlResult = ensureCodexConfig({
|
|
14151
14168
|
vkBaseUrl,
|
|
14152
|
-
skipVk,
|
|
14169
|
+
skipVk: true,
|
|
14153
14170
|
dryRun: !allowRuntimeCodexMutation,
|
|
14154
14171
|
});
|
|
14155
14172
|
if (!tomlResult.noChanges) {
|
|
@@ -15189,7 +15206,7 @@ injectMonitorFunctions({
|
|
|
15189
15206
|
},
|
|
15190
15207
|
});
|
|
15191
15208
|
if (telegramBotEnabled) {
|
|
15192
|
-
void startTelegramBot();
|
|
15209
|
+
void startTelegramBot(getTelegramBotStartOptions());
|
|
15193
15210
|
|
|
15194
15211
|
// Process any commands queued by telegram-sentinel while monitor was down
|
|
15195
15212
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.7",
|
|
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",
|
|
@@ -185,6 +185,7 @@
|
|
|
185
185
|
"task-assessment.mjs",
|
|
186
186
|
"task-complexity.mjs",
|
|
187
187
|
"task-claims.mjs",
|
|
188
|
+
"task-context.mjs",
|
|
188
189
|
"task-attachments.mjs",
|
|
189
190
|
"task-executor.mjs",
|
|
190
191
|
"task-store.mjs",
|