bosun 0.34.4 → 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/agent-endpoint.mjs +2 -2
- package/agent-event-bus.mjs +3 -3
- package/agent-work-report.mjs +1 -1
- package/codex-config.mjs +242 -0
- package/config.mjs +54 -8
- package/maintenance.mjs +73 -6
- package/monitor.mjs +286 -45
- package/package.json +1 -1
- package/primary-agent.mjs +113 -17
- package/repo-config.mjs +38 -3
- package/setup-web-server.mjs +56 -15
- package/setup.mjs +31 -22
- package/task-executor.mjs +95 -21
- package/ui/app.js +2 -2
- package/ui/app.monolith.js +1 -0
- package/ui/components/agent-selector.js +192 -141
- package/ui/components/charts.js +10 -11
- package/ui/components/chat-view.js +107 -77
- package/ui/modules/agent-display.js +1 -1
- package/ui/modules/settings-schema.js +3 -0
- package/ui/modules/streaming.js +28 -14
- package/ui/setup.html +108 -27
- package/ui/styles/components.css +15 -0
- package/ui/styles/kanban.css +2 -2
- package/ui/styles/layout.css +12 -3
- package/ui/styles/sessions.css +137 -0
- package/ui/styles/variables.css +375 -0
- package/ui/tabs/chat.js +2 -2
- package/ui/tabs/control.js +1 -1
- package/ui/tabs/workflows.js +234 -20
- package/ui-server.mjs +316 -99
- 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 +237 -237
- package/workflow-templates.mjs +7 -1
package/agent-endpoint.mjs
CHANGED
|
@@ -30,7 +30,7 @@ const TAG = "[agent-endpoint]";
|
|
|
30
30
|
const DEFAULT_PORT = 18432;
|
|
31
31
|
const MAX_BODY_SIZE = 1024 * 1024; // 1 MB
|
|
32
32
|
const REQUEST_TIMEOUT_MS = 30_000; // 30 seconds
|
|
33
|
-
const ACCESS_DENIED_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes
|
|
33
|
+
const ACCESS_DENIED_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes
|
|
34
34
|
const BOSUN_ROOT_HINT = __dirname.toLowerCase().replace(/\\/g, '/');
|
|
35
35
|
|
|
36
36
|
// Valid status transitions when an agent self-reports
|
|
@@ -1387,4 +1387,4 @@ export class AgentEndpoint {
|
|
|
1387
1387
|
export function createAgentEndpoint(options) {
|
|
1388
1388
|
return new AgentEndpoint(options);
|
|
1389
1389
|
}
|
|
1390
|
-
|
|
1390
|
+
|
package/agent-event-bus.mjs
CHANGED
|
@@ -112,8 +112,8 @@ export class AgentEventBus {
|
|
|
112
112
|
options.staleThresholdMs || DEFAULTS.staleThresholdMs;
|
|
113
113
|
this._staleCheckIntervalMs =
|
|
114
114
|
options.staleCheckIntervalMs || DEFAULTS.staleCheckIntervalMs;
|
|
115
|
-
this._maxAutoRetries =
|
|
116
|
-
options.maxAutoRetries ?? DEFAULTS.maxAutoRetries;
|
|
115
|
+
this._maxAutoRetries =
|
|
116
|
+
options.maxAutoRetries ?? DEFAULTS.maxAutoRetries;
|
|
117
117
|
this._dedupeWindowMs = options.dedupeWindowMs || DEFAULTS.dedupeWindowMs;
|
|
118
118
|
|
|
119
119
|
/** @type {Array<{type: string, taskId: string, payload: object, ts: number}>} ring buffer */
|
|
@@ -203,7 +203,7 @@ export class AgentEventBus {
|
|
|
203
203
|
|
|
204
204
|
// ── Dedup
|
|
205
205
|
const key = `${type}:${taskId}`;
|
|
206
|
-
const last = this._recentEmits.get(key);
|
|
206
|
+
const last = this._recentEmits.get(key);
|
|
207
207
|
if (typeof last === "number" && ts - last < this._dedupeWindowMs) return;
|
|
208
208
|
this._recentEmits.set(key, ts);
|
|
209
209
|
if (this._recentEmits.size > 200) {
|
package/agent-work-report.mjs
CHANGED
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/maintenance.mjs
CHANGED
|
@@ -888,26 +888,93 @@ export function syncLocalTrackingBranches(repoRoot, branches) {
|
|
|
888
888
|
);
|
|
889
889
|
if (remoteCheck.status !== 0) continue;
|
|
890
890
|
|
|
891
|
-
//
|
|
891
|
+
// Measure divergence in both directions up front
|
|
892
892
|
const behindCheck = spawnSync(
|
|
893
893
|
"git",
|
|
894
894
|
["rev-list", "--count", `${branch}..${remoteRef}`],
|
|
895
895
|
{ cwd: repoRoot, encoding: "utf8", timeout: 5000, windowsHide: true },
|
|
896
896
|
);
|
|
897
897
|
const behind = parseInt(behindCheck.stdout?.trim(), 10) || 0;
|
|
898
|
-
if (behind === 0) continue; // Already up to date
|
|
899
898
|
|
|
900
|
-
// Check if local has commits not in remote (diverged)
|
|
901
899
|
const aheadCheck = spawnSync(
|
|
902
900
|
"git",
|
|
903
901
|
["rev-list", "--count", `${remoteRef}..${branch}`],
|
|
904
902
|
{ cwd: repoRoot, encoding: "utf8", timeout: 5000, windowsHide: true },
|
|
905
903
|
);
|
|
906
904
|
const ahead = parseInt(aheadCheck.stdout?.trim(), 10) || 0;
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
905
|
+
|
|
906
|
+
if (behind === 0 && ahead === 0) continue; // Already in sync
|
|
907
|
+
|
|
908
|
+
// Local is ahead of remote but not behind — try a plain push
|
|
909
|
+
if (behind === 0 && ahead > 0) {
|
|
910
|
+
const push = spawnSync(
|
|
911
|
+
"git",
|
|
912
|
+
["push", "origin", `${branch}:refs/heads/${branch}`, "--quiet"],
|
|
913
|
+
{ cwd: repoRoot, encoding: "utf8", timeout: 30_000, windowsHide: true },
|
|
910
914
|
);
|
|
915
|
+
if (push.status === 0) {
|
|
916
|
+
console.log(
|
|
917
|
+
`[maintenance] pushed local '${branch}' to origin (${ahead} commit(s) ahead)`,
|
|
918
|
+
);
|
|
919
|
+
synced++;
|
|
920
|
+
} else {
|
|
921
|
+
console.warn(
|
|
922
|
+
`[maintenance] git push '${branch}' failed: ${(push.stderr || push.stdout || "").toString().trim()}`,
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Truly diverged: local has unique commits AND is missing remote commits.
|
|
929
|
+
// Attempt rebase onto remote then push (checked-out branch only).
|
|
930
|
+
if (ahead > 0) {
|
|
931
|
+
const statusCheck = spawnSync("git", ["status", "--porcelain"], {
|
|
932
|
+
cwd: repoRoot,
|
|
933
|
+
encoding: "utf8",
|
|
934
|
+
timeout: 5000,
|
|
935
|
+
windowsHide: true,
|
|
936
|
+
});
|
|
937
|
+
if (statusCheck.stdout?.trim()) {
|
|
938
|
+
console.warn(
|
|
939
|
+
`[maintenance] local '${branch}' diverged (${ahead}↑ ${behind}↓) but has uncommitted changes — skipping`,
|
|
940
|
+
);
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (branch === currentBranch) {
|
|
944
|
+
const rebase = spawnSync(
|
|
945
|
+
"git",
|
|
946
|
+
["rebase", remoteRef, "--quiet"],
|
|
947
|
+
{ cwd: repoRoot, encoding: "utf8", timeout: 60_000, windowsHide: true },
|
|
948
|
+
);
|
|
949
|
+
if (rebase.status !== 0) {
|
|
950
|
+
spawnSync("git", ["rebase", "--abort"], {
|
|
951
|
+
cwd: repoRoot, timeout: 10_000, windowsHide: true,
|
|
952
|
+
});
|
|
953
|
+
console.warn(
|
|
954
|
+
`[maintenance] rebase of '${branch}' onto ${remoteRef} failed (${ahead}↑ ${behind}↓) — skipping`,
|
|
955
|
+
);
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
const push = spawnSync(
|
|
959
|
+
"git",
|
|
960
|
+
["push", "origin", `${branch}:refs/heads/${branch}`, "--quiet"],
|
|
961
|
+
{ cwd: repoRoot, encoding: "utf8", timeout: 30_000, windowsHide: true },
|
|
962
|
+
);
|
|
963
|
+
if (push.status === 0) {
|
|
964
|
+
console.log(
|
|
965
|
+
`[maintenance] rebased and pushed '${branch}' (was ${ahead}↑ ${behind}↓)`,
|
|
966
|
+
);
|
|
967
|
+
synced++;
|
|
968
|
+
} else {
|
|
969
|
+
console.warn(
|
|
970
|
+
`[maintenance] push after rebase of '${branch}' failed: ${(push.stderr || push.stdout || "").toString().trim()}`,
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
} else {
|
|
974
|
+
console.warn(
|
|
975
|
+
`[maintenance] local '${branch}' diverged (${ahead}↑ ${behind}↓) and not checked out — skipping (rebase requires checkout)`,
|
|
976
|
+
);
|
|
977
|
+
}
|
|
911
978
|
continue;
|
|
912
979
|
}
|
|
913
980
|
|