bosun 0.34.5 → 0.34.6
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/codex-config.mjs +242 -0
- package/config.mjs +54 -8
- package/monitor.mjs +100 -2
- package/package.json +1 -1
- package/primary-agent.mjs +112 -18
- package/repo-config.mjs +38 -3
- package/task-executor.mjs +95 -21
- package/ui/components/agent-selector.js +11 -6
- package/ui/components/chat-view.js +107 -77
- package/ui/setup.html +104 -22
- package/ui/styles/sessions.css +113 -0
- package/ui-server.mjs +21 -14
- package/workflow-engine.mjs +14 -0
- package/workflow-nodes.mjs +176 -42
- package/workflow-templates/agents.mjs +70 -4
- package/workflow-templates/ci-cd.mjs +14 -7
- package/workflow-templates/github.mjs +21 -9
- package/workflow-templates/reliability.mjs +283 -0
- package/workflow-templates/security.mjs +5 -5
- package/workflow-templates.mjs +7 -1
package/codex-config.mjs
CHANGED
|
@@ -887,14 +887,17 @@ export function buildCommonMcpBlocks() {
|
|
|
887
887
|
"",
|
|
888
888
|
"# ── Common MCP servers (added by bosun) ──",
|
|
889
889
|
"[mcp_servers.context7]",
|
|
890
|
+
"startup_timeout_sec = 120",
|
|
890
891
|
'command = "npx"',
|
|
891
892
|
'args = ["-y", "@upstash/context7-mcp"]',
|
|
892
893
|
"",
|
|
893
894
|
"[mcp_servers.sequential-thinking]",
|
|
895
|
+
"startup_timeout_sec = 120",
|
|
894
896
|
'command = "npx"',
|
|
895
897
|
'args = ["-y", "@modelcontextprotocol/server-sequential-thinking"]',
|
|
896
898
|
"",
|
|
897
899
|
"[mcp_servers.playwright]",
|
|
900
|
+
"startup_timeout_sec = 120",
|
|
898
901
|
'command = "npx"',
|
|
899
902
|
'args = ["-y", "@playwright/mcp@latest"]',
|
|
900
903
|
"",
|
|
@@ -914,6 +917,44 @@ function hasNamedMcpServer(toml, name) {
|
|
|
914
917
|
);
|
|
915
918
|
}
|
|
916
919
|
|
|
920
|
+
function ensureMcpStartupTimeout(toml, name, timeoutSec = 120) {
|
|
921
|
+
const header = `[mcp_servers.${name}]`;
|
|
922
|
+
const headerIdx = toml.indexOf(header);
|
|
923
|
+
if (headerIdx === -1) return { toml, changed: false };
|
|
924
|
+
|
|
925
|
+
const afterHeader = headerIdx + header.length;
|
|
926
|
+
const nextSection = toml.indexOf("\n[", afterHeader);
|
|
927
|
+
const sectionEnd = nextSection === -1 ? toml.length : nextSection;
|
|
928
|
+
let section = toml.substring(afterHeader, sectionEnd);
|
|
929
|
+
|
|
930
|
+
const timeoutRegex = /^startup_timeout_sec\s*=\s*\d+.*$/m;
|
|
931
|
+
let changed = false;
|
|
932
|
+
if (timeoutRegex.test(section)) {
|
|
933
|
+
const desired = `startup_timeout_sec = ${timeoutSec}`;
|
|
934
|
+
const updated = section.replace(timeoutRegex, desired);
|
|
935
|
+
if (updated !== section) {
|
|
936
|
+
section = updated;
|
|
937
|
+
changed = true;
|
|
938
|
+
}
|
|
939
|
+
} else {
|
|
940
|
+
section = section.trimEnd() + `\nstartup_timeout_sec = ${timeoutSec}\n`;
|
|
941
|
+
changed = true;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
if (!changed) return { toml, changed: false };
|
|
945
|
+
return {
|
|
946
|
+
toml: toml.substring(0, afterHeader) + section + toml.substring(sectionEnd),
|
|
947
|
+
changed: true,
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function stripDeprecatedSandboxPermissions(toml) {
|
|
952
|
+
return String(toml || "").replace(
|
|
953
|
+
/^\s*sandbox_permissions\s*=.*(?:\r?\n)?/gim,
|
|
954
|
+
"",
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
917
958
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
918
959
|
|
|
919
960
|
/**
|
|
@@ -1298,6 +1339,207 @@ export function ensureCodexConfig({
|
|
|
1298
1339
|
noChanges: true,
|
|
1299
1340
|
};
|
|
1300
1341
|
|
|
1342
|
+
const configExisted = existsSync(CONFIG_PATH);
|
|
1343
|
+
const originalToml = readCodexConfig();
|
|
1344
|
+
let toml = stripDeprecatedSandboxPermissions(originalToml);
|
|
1345
|
+
if (!configExisted) {
|
|
1346
|
+
result.created = true;
|
|
1347
|
+
toml = "";
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const sandboxModeResult = ensureTopLevelSandboxMode(
|
|
1351
|
+
toml,
|
|
1352
|
+
env.CODEX_SANDBOX_MODE,
|
|
1353
|
+
);
|
|
1354
|
+
toml = sandboxModeResult.toml;
|
|
1355
|
+
if (sandboxModeResult.changed) {
|
|
1356
|
+
result.sandboxAdded = true;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
const repoRoot =
|
|
1360
|
+
env.BOSUN_AGENT_REPO_ROOT ||
|
|
1361
|
+
env.REPO_ROOT ||
|
|
1362
|
+
env.BOSUN_HOME ||
|
|
1363
|
+
process.cwd();
|
|
1364
|
+
const additionalRoots = env.BOSUN_WORKSPACES_DIR
|
|
1365
|
+
? [env.BOSUN_WORKSPACES_DIR]
|
|
1366
|
+
: [];
|
|
1367
|
+
const sandboxWorkspaceResult = ensureSandboxWorkspaceWrite(toml, {
|
|
1368
|
+
repoRoot,
|
|
1369
|
+
additionalRoots,
|
|
1370
|
+
writableRoots: env.CODEX_SANDBOX_WRITABLE_ROOTS,
|
|
1371
|
+
});
|
|
1372
|
+
toml = sandboxWorkspaceResult.toml;
|
|
1373
|
+
result.sandboxWorkspaceAdded = sandboxWorkspaceResult.added;
|
|
1374
|
+
result.sandboxWorkspaceUpdated =
|
|
1375
|
+
sandboxWorkspaceResult.changed && !sandboxWorkspaceResult.added;
|
|
1376
|
+
result.sandboxWorkspaceRootsAdded = sandboxWorkspaceResult.rootsAdded;
|
|
1377
|
+
|
|
1378
|
+
const pruneResult = pruneStaleSandboxRoots(toml);
|
|
1379
|
+
toml = pruneResult.toml;
|
|
1380
|
+
result.sandboxStaleRootsRemoved = pruneResult.removed;
|
|
1381
|
+
|
|
1382
|
+
if (!hasShellEnvPolicy(toml)) {
|
|
1383
|
+
toml += buildShellEnvPolicy(env.CODEX_SHELL_ENV_POLICY || "all");
|
|
1384
|
+
result.shellEnvAdded = true;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
const rawPrimary = String(primarySdk || env.PRIMARY_AGENT || "codex")
|
|
1388
|
+
.trim()
|
|
1389
|
+
.toLowerCase();
|
|
1390
|
+
const normalizedPrimary =
|
|
1391
|
+
rawPrimary === "copilot" || rawPrimary.includes("copilot")
|
|
1392
|
+
? "copilot"
|
|
1393
|
+
: rawPrimary === "claude" || rawPrimary.includes("claude")
|
|
1394
|
+
? "claude"
|
|
1395
|
+
: rawPrimary === "codex" || rawPrimary.includes("codex")
|
|
1396
|
+
? "codex"
|
|
1397
|
+
: "codex";
|
|
1398
|
+
if (!hasAgentSdkConfig(toml)) {
|
|
1399
|
+
toml += buildAgentSdkBlock({ primary: normalizedPrimary });
|
|
1400
|
+
result.agentSdkAdded = true;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
const maxThreads = resolveAgentMaxThreads(env);
|
|
1404
|
+
if (maxThreads.explicit && !maxThreads.value) {
|
|
1405
|
+
result.agentMaxThreadsSkipped = String(maxThreads.raw);
|
|
1406
|
+
} else {
|
|
1407
|
+
const maxThreadsResult = ensureAgentMaxThreads(toml, {
|
|
1408
|
+
maxThreads: maxThreads.value,
|
|
1409
|
+
overwrite: maxThreads.explicit,
|
|
1410
|
+
});
|
|
1411
|
+
toml = maxThreadsResult.toml;
|
|
1412
|
+
if (maxThreadsResult.changed && !maxThreadsResult.skipped) {
|
|
1413
|
+
result.agentMaxThreads = {
|
|
1414
|
+
from: maxThreadsResult.existing,
|
|
1415
|
+
to: maxThreadsResult.applied,
|
|
1416
|
+
explicit: maxThreads.explicit,
|
|
1417
|
+
};
|
|
1418
|
+
} else if (maxThreadsResult.skipped && maxThreads.explicit) {
|
|
1419
|
+
result.agentMaxThreadsSkipped = String(maxThreads.raw);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
const featureResult = ensureFeatureFlags(toml, env);
|
|
1424
|
+
result.featuresAdded = featureResult.added;
|
|
1425
|
+
toml = featureResult.toml;
|
|
1426
|
+
|
|
1427
|
+
if (skipVk) {
|
|
1428
|
+
if (hasVibeKanbanMcp(toml)) {
|
|
1429
|
+
toml = removeVibeKanbanMcp(toml);
|
|
1430
|
+
result.vkRemoved = true;
|
|
1431
|
+
}
|
|
1432
|
+
} else if (!hasVibeKanbanMcp(toml)) {
|
|
1433
|
+
toml += buildVibeKanbanBlock({ vkBaseUrl });
|
|
1434
|
+
result.vkAdded = true;
|
|
1435
|
+
} else {
|
|
1436
|
+
const vkEnvValues = {
|
|
1437
|
+
VK_BASE_URL: vkBaseUrl,
|
|
1438
|
+
VK_ENDPOINT_URL: vkBaseUrl,
|
|
1439
|
+
};
|
|
1440
|
+
const beforeVkEnv = toml;
|
|
1441
|
+
if (!hasVibeKanbanEnv(toml)) {
|
|
1442
|
+
toml =
|
|
1443
|
+
toml.trimEnd() +
|
|
1444
|
+
"\n\n[mcp_servers.vibe_kanban.env]\n" +
|
|
1445
|
+
`VK_BASE_URL = "${vkBaseUrl}"\n` +
|
|
1446
|
+
`VK_ENDPOINT_URL = "${vkBaseUrl}"\n`;
|
|
1447
|
+
} else {
|
|
1448
|
+
toml = updateVibeKanbanEnv(toml, vkEnvValues);
|
|
1449
|
+
}
|
|
1450
|
+
if (toml !== beforeVkEnv) {
|
|
1451
|
+
result.vkEnvUpdated = true;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
const commonMcpBlocks = [
|
|
1456
|
+
{
|
|
1457
|
+
present: hasContext7Mcp(toml),
|
|
1458
|
+
block: [
|
|
1459
|
+
"",
|
|
1460
|
+
"# ── Common MCP servers (added by bosun) ──",
|
|
1461
|
+
"[mcp_servers.context7]",
|
|
1462
|
+
"startup_timeout_sec = 120",
|
|
1463
|
+
'command = "npx"',
|
|
1464
|
+
'args = ["-y", "@upstash/context7-mcp"]',
|
|
1465
|
+
"",
|
|
1466
|
+
].join("\n"),
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
present: hasNamedMcpServer(toml, "sequential-thinking"),
|
|
1470
|
+
block: [
|
|
1471
|
+
"",
|
|
1472
|
+
"[mcp_servers.sequential-thinking]",
|
|
1473
|
+
"startup_timeout_sec = 120",
|
|
1474
|
+
'command = "npx"',
|
|
1475
|
+
'args = ["-y", "@modelcontextprotocol/server-sequential-thinking"]',
|
|
1476
|
+
"",
|
|
1477
|
+
].join("\n"),
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
present: hasNamedMcpServer(toml, "playwright"),
|
|
1481
|
+
block: [
|
|
1482
|
+
"",
|
|
1483
|
+
"[mcp_servers.playwright]",
|
|
1484
|
+
"startup_timeout_sec = 120",
|
|
1485
|
+
'command = "npx"',
|
|
1486
|
+
'args = ["-y", "@playwright/mcp@latest"]',
|
|
1487
|
+
"",
|
|
1488
|
+
].join("\n"),
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
present: hasMicrosoftDocsMcp(toml),
|
|
1492
|
+
block: [
|
|
1493
|
+
"",
|
|
1494
|
+
"[mcp_servers.microsoft-docs]",
|
|
1495
|
+
'url = "https://learn.microsoft.com/api/mcp"',
|
|
1496
|
+
'tools = ["microsoft_docs_search", "microsoft_code_sample_search"]',
|
|
1497
|
+
"",
|
|
1498
|
+
].join("\n"),
|
|
1499
|
+
},
|
|
1500
|
+
];
|
|
1501
|
+
for (const item of commonMcpBlocks) {
|
|
1502
|
+
if (item.present) continue;
|
|
1503
|
+
toml += item.block;
|
|
1504
|
+
result.commonMcpAdded = true;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
for (const serverName of ["context7", "sequential-thinking", "playwright"]) {
|
|
1508
|
+
const timeoutResult = ensureMcpStartupTimeout(toml, serverName, 120);
|
|
1509
|
+
toml = timeoutResult.toml;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const providerResult = ensureModelProviderSectionsFromEnv(toml, env);
|
|
1513
|
+
toml = providerResult.toml;
|
|
1514
|
+
result.profileProvidersAdded = providerResult.added;
|
|
1515
|
+
|
|
1516
|
+
const timeoutAudit = auditStreamTimeouts(toml);
|
|
1517
|
+
for (const item of timeoutAudit) {
|
|
1518
|
+
if (!item.needsUpdate) continue;
|
|
1519
|
+
toml = setStreamTimeout(toml, item.provider, RECOMMENDED_STREAM_IDLE_TIMEOUT_MS);
|
|
1520
|
+
result.timeoutsFixed.push({
|
|
1521
|
+
provider: item.provider,
|
|
1522
|
+
from: item.currentValue,
|
|
1523
|
+
to: RECOMMENDED_STREAM_IDLE_TIMEOUT_MS,
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const providers = auditStreamTimeouts(toml).map((item) => item.provider);
|
|
1528
|
+
for (const provider of providers) {
|
|
1529
|
+
const beforeRetry = toml;
|
|
1530
|
+
toml = ensureRetrySettings(toml, provider);
|
|
1531
|
+
if (toml !== beforeRetry) {
|
|
1532
|
+
result.retriesAdded.push(provider);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const changed = toml !== originalToml;
|
|
1537
|
+
result.noChanges = !result.created && !changed;
|
|
1538
|
+
|
|
1539
|
+
if (!dryRun && (result.created || changed)) {
|
|
1540
|
+
writeCodexConfig(toml);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1301
1543
|
return result;
|
|
1302
1544
|
}
|
|
1303
1545
|
|
package/config.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import { applyAllCompatibility } from "./compat.mjs";
|
|
|
29
29
|
import {
|
|
30
30
|
normalizeExecutorKey,
|
|
31
31
|
getModelsForExecutor,
|
|
32
|
+
MODEL_ALIASES,
|
|
32
33
|
} from "./task-complexity.mjs";
|
|
33
34
|
|
|
34
35
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -408,13 +409,43 @@ function parseListValue(value) {
|
|
|
408
409
|
.filter(Boolean);
|
|
409
410
|
}
|
|
410
411
|
|
|
411
|
-
function
|
|
412
|
+
function inferExecutorModelsFromVariant(executor, variant) {
|
|
413
|
+
const normalizedExecutor = normalizeExecutorKey(executor);
|
|
414
|
+
if (!normalizedExecutor) return [];
|
|
415
|
+
const normalizedVariant = String(variant || "DEFAULT")
|
|
416
|
+
.trim()
|
|
417
|
+
.toUpperCase();
|
|
418
|
+
if (!normalizedVariant || normalizedVariant === "DEFAULT") return [];
|
|
419
|
+
|
|
420
|
+
const known = getModelsForExecutor(normalizedExecutor);
|
|
421
|
+
const inferred = known.filter((model) => {
|
|
422
|
+
const alias = MODEL_ALIASES[model];
|
|
423
|
+
return (
|
|
424
|
+
String(alias?.variant || "")
|
|
425
|
+
.trim()
|
|
426
|
+
.toUpperCase() === normalizedVariant
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
if (inferred.length > 0) return inferred;
|
|
430
|
+
|
|
431
|
+
// Fallback for variants encoded as model slug with underscores.
|
|
432
|
+
const slugGuess = normalizedVariant.toLowerCase().replaceAll("_", "-");
|
|
433
|
+
if (known.includes(slugGuess)) return [slugGuess];
|
|
434
|
+
|
|
435
|
+
return [];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function normalizeExecutorModels(executor, models, variant = "DEFAULT") {
|
|
412
439
|
const normalizedExecutor = normalizeExecutorKey(executor);
|
|
413
440
|
if (!normalizedExecutor) return [];
|
|
414
441
|
const input = parseListValue(models);
|
|
415
442
|
const known = new Set(getModelsForExecutor(normalizedExecutor));
|
|
416
443
|
if (input.length === 0) {
|
|
417
|
-
|
|
444
|
+
const inferred = inferExecutorModelsFromVariant(
|
|
445
|
+
normalizedExecutor,
|
|
446
|
+
variant,
|
|
447
|
+
);
|
|
448
|
+
return inferred.length > 0 ? inferred : [...known];
|
|
418
449
|
}
|
|
419
450
|
return input.filter((model) => known.has(model));
|
|
420
451
|
}
|
|
@@ -433,7 +464,7 @@ function normalizeExecutorEntry(entry, index = 0, total = 1) {
|
|
|
433
464
|
const name =
|
|
434
465
|
String(entry.name || "").trim() ||
|
|
435
466
|
`${normalized}-${String(variant || "default").toLowerCase()}`;
|
|
436
|
-
const models = normalizeExecutorModels(executorType, entry.models);
|
|
467
|
+
const models = normalizeExecutorModels(executorType, entry.models, variant);
|
|
437
468
|
const codexProfile = String(
|
|
438
469
|
entry.codexProfile || entry.modelProfile || "",
|
|
439
470
|
).trim();
|
|
@@ -723,7 +754,11 @@ function parseExecutorsFromEnv() {
|
|
|
723
754
|
const parts = entries[i].split(":");
|
|
724
755
|
if (parts.length < 2) continue;
|
|
725
756
|
const executorType = parts[0].toUpperCase();
|
|
726
|
-
const models = normalizeExecutorModels(
|
|
757
|
+
const models = normalizeExecutorModels(
|
|
758
|
+
executorType,
|
|
759
|
+
parts[3] || "",
|
|
760
|
+
parts[1] || "DEFAULT",
|
|
761
|
+
);
|
|
727
762
|
executors.push({
|
|
728
763
|
name: `${parts[0].toLowerCase()}-${parts[1].toLowerCase()}`,
|
|
729
764
|
executor: executorType,
|
|
@@ -857,12 +892,23 @@ function loadExecutorConfig(configDir, configData) {
|
|
|
857
892
|
|
|
858
893
|
for (let index = 0; index < executors.length; index++) {
|
|
859
894
|
const current = executors[index];
|
|
860
|
-
if (current.codexProfile) continue;
|
|
861
895
|
const match = findExecutorMetadataMatch(current, fileExecutors, index);
|
|
862
|
-
if (!match
|
|
896
|
+
if (!match) continue;
|
|
897
|
+
const merged = { ...current };
|
|
898
|
+
if (typeof match.name === "string" && match.name.trim()) {
|
|
899
|
+
merged.name = match.name.trim();
|
|
900
|
+
}
|
|
901
|
+
if (typeof match.enabled === "boolean") {
|
|
902
|
+
merged.enabled = match.enabled;
|
|
903
|
+
}
|
|
904
|
+
if (Array.isArray(match.models) && match.models.length > 0) {
|
|
905
|
+
merged.models = [...new Set(match.models)];
|
|
906
|
+
}
|
|
907
|
+
if (match.codexProfile) {
|
|
908
|
+
merged.codexProfile = match.codexProfile;
|
|
909
|
+
}
|
|
863
910
|
executors[index] = {
|
|
864
|
-
...
|
|
865
|
-
codexProfile: match.codexProfile,
|
|
911
|
+
...merged,
|
|
866
912
|
};
|
|
867
913
|
}
|
|
868
914
|
}
|
package/monitor.mjs
CHANGED
|
@@ -478,8 +478,12 @@ async function ensureWorkflowAutomationEngine() {
|
|
|
478
478
|
delete payload.projectId;
|
|
479
479
|
return createTask(projectId, payload);
|
|
480
480
|
},
|
|
481
|
-
updateTaskStatus: async (taskId, status) =>
|
|
482
|
-
|
|
481
|
+
updateTaskStatus: async (taskId, status, options = {}) =>
|
|
482
|
+
updateTaskStatus(
|
|
483
|
+
String(taskId || ""),
|
|
484
|
+
String(status || ""),
|
|
485
|
+
options && typeof options === "object" ? options : {},
|
|
486
|
+
),
|
|
483
487
|
listTasks: async (projectId, filters = {}) =>
|
|
484
488
|
listKanbanTasks(String(projectId || ""), filters || {}),
|
|
485
489
|
};
|
|
@@ -4431,6 +4435,10 @@ async function updateTaskStatus(taskId, newStatus, options = {}) {
|
|
|
4431
4435
|
options?.taskData && typeof options.taskData === "object"
|
|
4432
4436
|
? options.taskData
|
|
4433
4437
|
: null;
|
|
4438
|
+
const workflowData =
|
|
4439
|
+
options?.workflowData && typeof options.workflowData === "object"
|
|
4440
|
+
? options.workflowData
|
|
4441
|
+
: null;
|
|
4434
4442
|
const taskTitle = String(
|
|
4435
4443
|
options?.taskTitle || taskData?.title || getInternalTask(normalizedTaskId)?.title || "",
|
|
4436
4444
|
).trim();
|
|
@@ -4444,6 +4452,43 @@ async function updateTaskStatus(taskId, newStatus, options = {}) {
|
|
|
4444
4452
|
taskTitle,
|
|
4445
4453
|
previousStatus: previousStatus || null,
|
|
4446
4454
|
status: normalizedStatus,
|
|
4455
|
+
branch: String(
|
|
4456
|
+
options?.branch ||
|
|
4457
|
+
taskData?.branch ||
|
|
4458
|
+
taskData?.branchName ||
|
|
4459
|
+
taskData?.meta?.branch_name ||
|
|
4460
|
+
workflowData?.branch ||
|
|
4461
|
+
"",
|
|
4462
|
+
).trim() || null,
|
|
4463
|
+
baseBranch: String(
|
|
4464
|
+
options?.baseBranch ||
|
|
4465
|
+
taskData?.baseBranch ||
|
|
4466
|
+
taskData?.base_branch ||
|
|
4467
|
+
taskData?.meta?.base_branch ||
|
|
4468
|
+
workflowData?.baseBranch ||
|
|
4469
|
+
"",
|
|
4470
|
+
).trim() || null,
|
|
4471
|
+
worktreePath: String(
|
|
4472
|
+
options?.worktreePath ||
|
|
4473
|
+
taskData?.worktreePath ||
|
|
4474
|
+
taskData?.meta?.worktreePath ||
|
|
4475
|
+
workflowData?.worktreePath ||
|
|
4476
|
+
"",
|
|
4477
|
+
).trim() || null,
|
|
4478
|
+
prNumber: String(
|
|
4479
|
+
options?.prNumber ||
|
|
4480
|
+
taskData?.prNumber ||
|
|
4481
|
+
taskData?.meta?.pr_number ||
|
|
4482
|
+
workflowData?.prNumber ||
|
|
4483
|
+
"",
|
|
4484
|
+
).trim() || null,
|
|
4485
|
+
prUrl: String(
|
|
4486
|
+
options?.prUrl ||
|
|
4487
|
+
taskData?.prUrl ||
|
|
4488
|
+
taskData?.meta?.pr_url ||
|
|
4489
|
+
workflowData?.prUrl ||
|
|
4490
|
+
"",
|
|
4491
|
+
).trim() || null,
|
|
4447
4492
|
};
|
|
4448
4493
|
const queueTaskStatusWorkflowEvents = () => {
|
|
4449
4494
|
const statusChanged = previousStatus !== normalizedStatus;
|
|
@@ -14536,6 +14581,29 @@ if (isExecutorDisabled()) {
|
|
|
14536
14581
|
},
|
|
14537
14582
|
onTaskCompleted: (task, result) => {
|
|
14538
14583
|
const taskId = String(task?.id || task?.task_id || "").trim();
|
|
14584
|
+
const branch = String(
|
|
14585
|
+
result?.branch ||
|
|
14586
|
+
task?.branchName ||
|
|
14587
|
+
task?.meta?.branch_name ||
|
|
14588
|
+
"",
|
|
14589
|
+
).trim() || null;
|
|
14590
|
+
const worktreePath = String(
|
|
14591
|
+
result?.worktreePath ||
|
|
14592
|
+
task?.worktreePath ||
|
|
14593
|
+
task?.meta?.worktreePath ||
|
|
14594
|
+
"",
|
|
14595
|
+
).trim() || null;
|
|
14596
|
+
const prNumber = result?.prNumber
|
|
14597
|
+
? String(result.prNumber)
|
|
14598
|
+
: null;
|
|
14599
|
+
const prUrl = String(result?.prUrl || "").trim() || null;
|
|
14600
|
+
const baseBranch = String(
|
|
14601
|
+
result?.baseBranch ||
|
|
14602
|
+
task?.baseBranch ||
|
|
14603
|
+
task?.base_branch ||
|
|
14604
|
+
task?.meta?.base_branch ||
|
|
14605
|
+
"",
|
|
14606
|
+
).trim() || null;
|
|
14539
14607
|
console.log(
|
|
14540
14608
|
`[task-executor] ✅ completed: "${task.title}" (${result.attempts} attempt(s))`,
|
|
14541
14609
|
);
|
|
@@ -14566,6 +14634,11 @@ if (isExecutorDisabled()) {
|
|
|
14566
14634
|
taskStatus: "completed",
|
|
14567
14635
|
attempts: Number(result?.attempts || 0),
|
|
14568
14636
|
success: result?.success !== false,
|
|
14637
|
+
branch,
|
|
14638
|
+
worktreePath,
|
|
14639
|
+
prNumber,
|
|
14640
|
+
prUrl,
|
|
14641
|
+
baseBranch,
|
|
14569
14642
|
},
|
|
14570
14643
|
{ dedupKey: `workflow-event:task.completed:${taskId}:${result?.attempts || 0}` },
|
|
14571
14644
|
);
|
|
@@ -14573,6 +14646,27 @@ if (isExecutorDisabled()) {
|
|
|
14573
14646
|
},
|
|
14574
14647
|
onTaskFailed: (task, err) => {
|
|
14575
14648
|
const taskId = String(task?.id || task?.task_id || "").trim();
|
|
14649
|
+
const branch = String(
|
|
14650
|
+
err?.branch ||
|
|
14651
|
+
task?.branchName ||
|
|
14652
|
+
task?.meta?.branch_name ||
|
|
14653
|
+
"",
|
|
14654
|
+
).trim() || null;
|
|
14655
|
+
const worktreePath = String(
|
|
14656
|
+
err?.worktreePath ||
|
|
14657
|
+
task?.worktreePath ||
|
|
14658
|
+
task?.meta?.worktreePath ||
|
|
14659
|
+
"",
|
|
14660
|
+
).trim() || null;
|
|
14661
|
+
const baseBranch = String(
|
|
14662
|
+
err?.baseBranch ||
|
|
14663
|
+
task?.baseBranch ||
|
|
14664
|
+
task?.base_branch ||
|
|
14665
|
+
task?.meta?.base_branch ||
|
|
14666
|
+
"",
|
|
14667
|
+
).trim() || null;
|
|
14668
|
+
const attempts =
|
|
14669
|
+
Number(err?.attempts || 0) > 0 ? Number(err.attempts) : null;
|
|
14576
14670
|
console.warn(
|
|
14577
14671
|
`[task-executor] ❌ failed: "${task.title}" — ${formatMonitorError(err)}`,
|
|
14578
14672
|
);
|
|
@@ -14586,6 +14680,10 @@ if (isExecutorDisabled()) {
|
|
|
14586
14680
|
taskTitle: task?.title || "",
|
|
14587
14681
|
taskStatus: "failed",
|
|
14588
14682
|
error: errorMessage,
|
|
14683
|
+
branch,
|
|
14684
|
+
worktreePath,
|
|
14685
|
+
baseBranch,
|
|
14686
|
+
attempts,
|
|
14589
14687
|
},
|
|
14590
14688
|
{ dedupKey: `workflow-event:task.failed:${taskId}:${errorMessage}` },
|
|
14591
14689
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.6",
|
|
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",
|