bosun 0.41.1 → 0.41.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.2",
|
|
4
4
|
"description": "Bosun Autonomous Engineering — manages AI agent executors with failover, extremely powerful workflow builder, and a massive amount of included default workflow templates for autonomous engineering, 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",
|
|
@@ -67,6 +67,131 @@ const WORKFLOW_AGENT_EVENT_PREVIEW_LIMIT = (() => {
|
|
|
67
67
|
})();
|
|
68
68
|
const BOSUN_ATTACHED_PR_LABEL = "bosun-attached";
|
|
69
69
|
|
|
70
|
+
const HTML_TEXT_BREAK_TAGS = new Set([
|
|
71
|
+
"address",
|
|
72
|
+
"article",
|
|
73
|
+
"aside",
|
|
74
|
+
"blockquote",
|
|
75
|
+
"br",
|
|
76
|
+
"dd",
|
|
77
|
+
"div",
|
|
78
|
+
"dl",
|
|
79
|
+
"dt",
|
|
80
|
+
"figcaption",
|
|
81
|
+
"figure",
|
|
82
|
+
"footer",
|
|
83
|
+
"form",
|
|
84
|
+
"h1",
|
|
85
|
+
"h2",
|
|
86
|
+
"h3",
|
|
87
|
+
"h4",
|
|
88
|
+
"h5",
|
|
89
|
+
"h6",
|
|
90
|
+
"header",
|
|
91
|
+
"hr",
|
|
92
|
+
"li",
|
|
93
|
+
"main",
|
|
94
|
+
"nav",
|
|
95
|
+
"ol",
|
|
96
|
+
"p",
|
|
97
|
+
"pre",
|
|
98
|
+
"section",
|
|
99
|
+
"table",
|
|
100
|
+
"tbody",
|
|
101
|
+
"td",
|
|
102
|
+
"tfoot",
|
|
103
|
+
"th",
|
|
104
|
+
"thead",
|
|
105
|
+
"tr",
|
|
106
|
+
"ul",
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
function decodeHtmlEntities(value = "") {
|
|
110
|
+
return String(value).replace(/&(?:nbsp|amp|lt|gt|quot|apos|#39|#\d+|#x[0-9a-f]+);/gi, (entity) => {
|
|
111
|
+
const normalized = entity.toLowerCase();
|
|
112
|
+
switch (normalized) {
|
|
113
|
+
case " ":
|
|
114
|
+
return " ";
|
|
115
|
+
case "&":
|
|
116
|
+
return "&";
|
|
117
|
+
case "<":
|
|
118
|
+
return "<";
|
|
119
|
+
case ">":
|
|
120
|
+
return ">";
|
|
121
|
+
case """:
|
|
122
|
+
return '"';
|
|
123
|
+
case "'":
|
|
124
|
+
case "'":
|
|
125
|
+
return "'";
|
|
126
|
+
default:
|
|
127
|
+
if (normalized.startsWith("&#x")) {
|
|
128
|
+
return String.fromCodePoint(Number.parseInt(normalized.slice(3, -1), 16));
|
|
129
|
+
}
|
|
130
|
+
if (normalized.startsWith("&#")) {
|
|
131
|
+
return String.fromCodePoint(Number.parseInt(normalized.slice(2, -1), 10));
|
|
132
|
+
}
|
|
133
|
+
return entity;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function stripHtmlToText(html = "") {
|
|
139
|
+
const input = String(html ?? "");
|
|
140
|
+
let plain = "";
|
|
141
|
+
let index = 0;
|
|
142
|
+
let skippedTagName = null;
|
|
143
|
+
|
|
144
|
+
while (index < input.length) {
|
|
145
|
+
const tagStart = input.indexOf("<", index);
|
|
146
|
+
if (tagStart === -1) {
|
|
147
|
+
if (!skippedTagName) plain += input.slice(index);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!skippedTagName && tagStart > index) {
|
|
152
|
+
plain += input.slice(index, tagStart);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const tagEnd = input.indexOf(">", tagStart + 1);
|
|
156
|
+
if (tagEnd === -1) {
|
|
157
|
+
if (!skippedTagName) plain += input.slice(tagStart).replace(/</g, " ");
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const rawTag = input.slice(tagStart + 1, tagEnd).trim();
|
|
162
|
+
const loweredTag = rawTag.toLowerCase();
|
|
163
|
+
const isClosingTag = loweredTag.startsWith("/");
|
|
164
|
+
const normalizedTag = isClosingTag ? loweredTag.slice(1).trimStart() : loweredTag;
|
|
165
|
+
const tagName = normalizedTag.match(/^[a-z0-9]+/i)?.[0] ?? "";
|
|
166
|
+
|
|
167
|
+
if (skippedTagName) {
|
|
168
|
+
if (isClosingTag && tagName === skippedTagName) {
|
|
169
|
+
skippedTagName = null;
|
|
170
|
+
plain += " ";
|
|
171
|
+
}
|
|
172
|
+
index = tagEnd + 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (tagName === "script" || tagName === "style") {
|
|
177
|
+
if (!isClosingTag && !normalizedTag.endsWith("/")) {
|
|
178
|
+
skippedTagName = tagName;
|
|
179
|
+
}
|
|
180
|
+
index = tagEnd + 1;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (HTML_TEXT_BREAK_TAGS.has(tagName)) {
|
|
185
|
+
plain += " ";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
index = tagEnd + 1;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return decodeHtmlEntities(plain);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
70
195
|
const PORT_TYPE_DESCRIPTIONS = Object.freeze({
|
|
71
196
|
Any: "Wildcard payload",
|
|
72
197
|
TaskDef: "Task definition/context payload",
|
|
@@ -9517,8 +9642,57 @@ function isValidGitWorktreePath(worktreePath) {
|
|
|
9517
9642
|
}
|
|
9518
9643
|
}
|
|
9519
9644
|
|
|
9645
|
+
function resolveGitDirForWorktree(worktreePath) {
|
|
9646
|
+
if (!worktreePath || !existsSync(worktreePath)) return "";
|
|
9647
|
+
try {
|
|
9648
|
+
const topLevel = execGitArgsSync(["rev-parse", "--show-toplevel"], {
|
|
9649
|
+
cwd: worktreePath,
|
|
9650
|
+
encoding: "utf8",
|
|
9651
|
+
timeout: 5000,
|
|
9652
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
9653
|
+
}).trim();
|
|
9654
|
+
const normalize = (value) =>
|
|
9655
|
+
resolve(String(value || ""))
|
|
9656
|
+
.replace(/\\/g, "/")
|
|
9657
|
+
.replace(/\/+$/, "")
|
|
9658
|
+
.toLowerCase();
|
|
9659
|
+
if (normalize(topLevel) !== normalize(worktreePath)) return "";
|
|
9660
|
+
const gitDir = execGitArgsSync(["rev-parse", "--git-dir"], {
|
|
9661
|
+
cwd: worktreePath,
|
|
9662
|
+
encoding: "utf8",
|
|
9663
|
+
timeout: 5000,
|
|
9664
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
9665
|
+
}).trim();
|
|
9666
|
+
if (!gitDir) return "";
|
|
9667
|
+
return resolve(worktreePath, gitDir);
|
|
9668
|
+
} catch {
|
|
9669
|
+
return "";
|
|
9670
|
+
}
|
|
9671
|
+
}
|
|
9672
|
+
|
|
9673
|
+
function hasUnresolvedGitOperation(worktreePath) {
|
|
9674
|
+
if (!worktreePath || !existsSync(worktreePath)) return false;
|
|
9675
|
+
try {
|
|
9676
|
+
const gitDir = resolveGitDirForWorktree(worktreePath);
|
|
9677
|
+
if (!gitDir || !existsSync(gitDir)) return true;
|
|
9678
|
+
for (const marker of ["rebase-merge", "rebase-apply", "MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
|
|
9679
|
+
if (existsSync(resolve(gitDir, marker))) return true;
|
|
9680
|
+
}
|
|
9681
|
+
const unmerged = execGitArgsSync(["diff", "--name-only", "--diff-filter=U"], {
|
|
9682
|
+
cwd: worktreePath,
|
|
9683
|
+
encoding: "utf8",
|
|
9684
|
+
timeout: 5000,
|
|
9685
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
9686
|
+
}).trim();
|
|
9687
|
+
return Boolean(unmerged);
|
|
9688
|
+
} catch {
|
|
9689
|
+
return true;
|
|
9690
|
+
}
|
|
9691
|
+
}
|
|
9692
|
+
|
|
9520
9693
|
function cleanupBrokenManagedWorktree(repoRoot, worktreePath) {
|
|
9521
9694
|
if (!worktreePath) return;
|
|
9695
|
+
const linkedGitDir = resolveGitDirForWorktree(worktreePath);
|
|
9522
9696
|
try {
|
|
9523
9697
|
execGitArgsSync(["worktree", "remove", String(worktreePath), "--force"], {
|
|
9524
9698
|
cwd: repoRoot,
|
|
@@ -9534,6 +9708,13 @@ function cleanupBrokenManagedWorktree(repoRoot, worktreePath) {
|
|
|
9534
9708
|
} catch {
|
|
9535
9709
|
/* best-effort */
|
|
9536
9710
|
}
|
|
9711
|
+
try {
|
|
9712
|
+
if (linkedGitDir && existsSync(linkedGitDir)) {
|
|
9713
|
+
rmSync(linkedGitDir, { recursive: true, force: true });
|
|
9714
|
+
}
|
|
9715
|
+
} catch {
|
|
9716
|
+
/* best-effort */
|
|
9717
|
+
}
|
|
9537
9718
|
try {
|
|
9538
9719
|
execGitArgsSync(["worktree", "prune"], {
|
|
9539
9720
|
cwd: repoRoot,
|
|
@@ -10551,6 +10732,9 @@ registerBuiltinNodeType("action.acquire_worktree", {
|
|
|
10551
10732
|
if (!isValidGitWorktreePath(worktreePath)) {
|
|
10552
10733
|
ctx.log(node.id, `Managed worktree is invalid, recreating: ${worktreePath}`);
|
|
10553
10734
|
cleanupBrokenManagedWorktree(repoRoot, worktreePath);
|
|
10735
|
+
} else if (hasUnresolvedGitOperation(worktreePath)) {
|
|
10736
|
+
ctx.log(node.id, `Managed worktree has unresolved git state, recreating: ${worktreePath}`);
|
|
10737
|
+
cleanupBrokenManagedWorktree(repoRoot, worktreePath);
|
|
10554
10738
|
}
|
|
10555
10739
|
}
|
|
10556
10740
|
|
|
@@ -10566,14 +10750,20 @@ registerBuiltinNodeType("action.acquire_worktree", {
|
|
|
10566
10750
|
} catch {
|
|
10567
10751
|
/* rebase failures are non-fatal for reuse */
|
|
10568
10752
|
}
|
|
10753
|
+
if (existsSync(worktreePath) && hasUnresolvedGitOperation(worktreePath)) {
|
|
10754
|
+
ctx.log(node.id, `Managed worktree refresh left unresolved git state, recreating: ${worktreePath}`);
|
|
10755
|
+
cleanupBrokenManagedWorktree(repoRoot, worktreePath);
|
|
10756
|
+
}
|
|
10757
|
+
}
|
|
10758
|
+
if (existsSync(worktreePath)) {
|
|
10759
|
+
ctx.data.worktreePath = worktreePath;
|
|
10760
|
+
ctx.data._worktreeCreated = false;
|
|
10761
|
+
ctx.data._worktreeManaged = true;
|
|
10762
|
+
ctx.log(node.id, `Reusing worktree: ${worktreePath}`);
|
|
10763
|
+
const cleared1 = clearBlockedWorktreeIdentity(worktreePath);
|
|
10764
|
+
if (cleared1) ctx.log(node.id, `Cleared blocked test git identity from worktree: ${worktreePath}`);
|
|
10765
|
+
return { success: true, worktreePath, created: false, reused: true, branch, baseBranch };
|
|
10569
10766
|
}
|
|
10570
|
-
ctx.data.worktreePath = worktreePath;
|
|
10571
|
-
ctx.data._worktreeCreated = false;
|
|
10572
|
-
ctx.data._worktreeManaged = true;
|
|
10573
|
-
ctx.log(node.id, `Reusing worktree: ${worktreePath}`);
|
|
10574
|
-
const cleared1 = clearBlockedWorktreeIdentity(worktreePath);
|
|
10575
|
-
if (cleared1) ctx.log(node.id, `Cleared blocked test git identity from worktree: ${worktreePath}`);
|
|
10576
|
-
return { success: true, worktreePath, created: false, reused: true, branch, baseBranch };
|
|
10577
10767
|
}
|
|
10578
10768
|
|
|
10579
10769
|
// Create fresh worktree
|