bosun 0.40.9 → 0.40.10
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/.env.example +3 -0
- package/README.md +0 -20
- package/agent/fleet-coordinator.mjs +2 -6
- package/git/git-safety.mjs +96 -0
- package/git/sdk-conflict-resolver.mjs +2 -0
- package/infra/monitor.mjs +4 -1
- package/kanban/ve-orchestrator.ps1 +5 -0
- package/kanban/vk-error-resolver.mjs +52 -48
- package/package.json +1 -2
- package/server/ui-server.mjs +3 -2
- package/setup.mjs +5 -0
- package/task/task-executor.mjs +13 -2
- package/telegram/telegram-bot.mjs +33 -22
- package/ui/components/session-list.js +53 -22
- package/ui/styles/components.css +170 -7
- package/ui/tabs/agents.js +250 -86
- package/ui/tabs/dashboard.js +399 -40
- package/ui/tabs/tasks.js +255 -20
- package/ui/tabs/workflow-canvas-utils.mjs +182 -0
- package/ui/tabs/workflows.js +515 -175
- package/workflow/workflow-engine.mjs +77 -5
- package/workspace/context-cache.mjs +113 -4
- package/workspace/workspace-manager.mjs +6 -1
- package/workspace/worktree-manager.mjs +5 -4
package/.env.example
CHANGED
|
@@ -541,6 +541,9 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
|
|
|
541
541
|
# BOSUN_TASK_CONTEXT_MAX_COMMENTS=8
|
|
542
542
|
# BOSUN_TASK_CONTEXT_MAX_COMMENT_CHARS=1200
|
|
543
543
|
# BOSUN_TASK_CONTEXT_MAX_ATTACHMENTS=20
|
|
544
|
+
# Immediate cap for known high-volume git outputs in context cache.
|
|
545
|
+
# Set to 0 to disable the cap entirely (default: 8000).
|
|
546
|
+
# BOSUN_GIT_OUTPUT_MAX_CHARS=8000
|
|
544
547
|
# Max upload size for task/chat attachments (MB)
|
|
545
548
|
# BOSUN_ATTACHMENT_MAX_MB=25
|
|
546
549
|
|
package/README.md
CHANGED
|
@@ -149,26 +149,6 @@ Bosun enforces a strict quality pipeline in both local hooks and CI:
|
|
|
149
149
|
- **Demo load smoke test** runs in `npm test` and blocks push if `site/index.html` or `site/ui/demo.html` fails to load required assets.
|
|
150
150
|
- **Prepublish checks** validate package contents and release readiness.
|
|
151
151
|
|
|
152
|
-
### Codebase annotation audit
|
|
153
|
-
|
|
154
|
-
Use `bosun audit` to generate and validate repo-level annotations that help future agents navigate the codebase without extra runtime context:
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
# Coverage report for supported source files
|
|
158
|
-
bosun audit scan
|
|
159
|
-
|
|
160
|
-
# Add missing file summaries and risky-function warnings
|
|
161
|
-
bosun audit generate
|
|
162
|
-
bosun audit warn
|
|
163
|
-
|
|
164
|
-
# Rebuild lean manifests and the file responsibility index
|
|
165
|
-
bosun audit manifest
|
|
166
|
-
bosun audit index
|
|
167
|
-
bosun audit trim
|
|
168
|
-
|
|
169
|
-
# CI-safe conformity gate
|
|
170
|
-
bosun audit --ci
|
|
171
|
-
```
|
|
172
152
|
|
|
173
153
|
Notes:
|
|
174
154
|
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
selectCoordinator,
|
|
34
34
|
getPresenceState,
|
|
35
35
|
} from "../infra/presence.mjs";
|
|
36
|
+
import { sanitizeGitEnv } from "../git/git-safety.mjs";
|
|
36
37
|
|
|
37
38
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
38
39
|
|
|
@@ -57,11 +58,7 @@ function emitFleetEvent(eventType, eventData = {}, opts = {}) {
|
|
|
57
58
|
// ── Repo Fingerprinting ──────────────────────────────────────────────────────
|
|
58
59
|
|
|
59
60
|
function buildGitEnv() {
|
|
60
|
-
|
|
61
|
-
delete env.GIT_DIR;
|
|
62
|
-
delete env.GIT_WORK_TREE;
|
|
63
|
-
delete env.GIT_INDEX_FILE;
|
|
64
|
-
return env;
|
|
61
|
+
return sanitizeGitEnv();
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
/**
|
|
@@ -857,4 +854,3 @@ export function formatFleetSummary() {
|
|
|
857
854
|
|
|
858
855
|
return lines.join("\n");
|
|
859
856
|
}
|
|
860
|
-
|
package/git/git-safety.mjs
CHANGED
|
@@ -1,14 +1,98 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
|
|
3
|
+
const STRIPPED_GIT_ENV_KEYS = [
|
|
4
|
+
"GIT_DIR",
|
|
5
|
+
"GIT_WORK_TREE",
|
|
6
|
+
"GIT_COMMON_DIR",
|
|
7
|
+
"GIT_INDEX_FILE",
|
|
8
|
+
"GIT_OBJECT_DIRECTORY",
|
|
9
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
10
|
+
"GIT_NAMESPACE",
|
|
11
|
+
"GIT_PREFIX",
|
|
12
|
+
"GIT_SUPER_PREFIX",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const BLOCKED_TEST_GIT_IDENTITIES = new Set([
|
|
16
|
+
"test@example.com",
|
|
17
|
+
"bosun-tests@example.com",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const TEST_FIXTURE_SENTINEL_PATHS = new Set([
|
|
21
|
+
".github/agents/TaskPlanner.agent.md",
|
|
22
|
+
]);
|
|
23
|
+
|
|
3
24
|
function runGit(args, cwd, timeout = 15_000) {
|
|
4
25
|
return spawnSync("git", args, {
|
|
5
26
|
cwd,
|
|
6
27
|
encoding: "utf8",
|
|
7
28
|
timeout,
|
|
8
29
|
shell: false,
|
|
30
|
+
env: sanitizeGitEnv(),
|
|
9
31
|
});
|
|
10
32
|
}
|
|
11
33
|
|
|
34
|
+
export function sanitizeGitEnv(baseEnv = process.env, extraEnv = {}) {
|
|
35
|
+
const env = { ...baseEnv };
|
|
36
|
+
for (const key of STRIPPED_GIT_ENV_KEYS) {
|
|
37
|
+
delete env[key];
|
|
38
|
+
}
|
|
39
|
+
return { ...env, ...extraEnv };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getGitConfig(cwd, key) {
|
|
43
|
+
const result = runGit(["config", "--get", key], cwd, 5_000);
|
|
44
|
+
if (result.status !== 0) return "";
|
|
45
|
+
return String(result.stdout || "").trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function listTrackedFiles(cwd, ref = "HEAD") {
|
|
49
|
+
const result = runGit(["ls-tree", "-r", "--name-only", ref], cwd, 30_000);
|
|
50
|
+
if (result.status !== 0) return null;
|
|
51
|
+
const out = String(result.stdout || "").trim();
|
|
52
|
+
return out ? out.split("\n").filter(Boolean) : [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function collectBlockedIdentitySignals(cwd) {
|
|
56
|
+
const signals = [];
|
|
57
|
+
const envChecks = [
|
|
58
|
+
["GIT_AUTHOR_EMAIL", process.env.GIT_AUTHOR_EMAIL],
|
|
59
|
+
["GIT_COMMITTER_EMAIL", process.env.GIT_COMMITTER_EMAIL],
|
|
60
|
+
["VE_GIT_AUTHOR_EMAIL", process.env.VE_GIT_AUTHOR_EMAIL],
|
|
61
|
+
];
|
|
62
|
+
for (const [key, value] of envChecks) {
|
|
63
|
+
const email = String(value || "").trim().toLowerCase();
|
|
64
|
+
if (BLOCKED_TEST_GIT_IDENTITIES.has(email)) {
|
|
65
|
+
signals.push(`${key}=${email}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const configChecks = [
|
|
70
|
+
["git config user.email", getGitConfig(cwd, "user.email")],
|
|
71
|
+
["git config author.email", getGitConfig(cwd, "author.email")],
|
|
72
|
+
["git config committer.email", getGitConfig(cwd, "committer.email")],
|
|
73
|
+
];
|
|
74
|
+
for (const [label, value] of configChecks) {
|
|
75
|
+
const email = String(value || "").trim().toLowerCase();
|
|
76
|
+
if (BLOCKED_TEST_GIT_IDENTITIES.has(email)) {
|
|
77
|
+
signals.push(`${label}=${email}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return signals;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function detectKnownFixtureSignature(cwd) {
|
|
85
|
+
const trackedFiles = listTrackedFiles(cwd, "HEAD");
|
|
86
|
+
if (!trackedFiles) return null;
|
|
87
|
+
const sentinelHits = trackedFiles.filter((file) => TEST_FIXTURE_SENTINEL_PATHS.has(file));
|
|
88
|
+
if (sentinelHits.length === 0) return null;
|
|
89
|
+
if (trackedFiles.length > 10) return null;
|
|
90
|
+
return {
|
|
91
|
+
trackedFiles: trackedFiles.length,
|
|
92
|
+
sentinels: sentinelHits,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
12
96
|
function countTrackedFiles(cwd, ref) {
|
|
13
97
|
const result = runGit(["ls-tree", "-r", "--name-only", ref], cwd, 30_000);
|
|
14
98
|
if (result.status !== 0) return null;
|
|
@@ -129,6 +213,18 @@ export function evaluateBranchSafetyForPush(worktreePath, opts = {}) {
|
|
|
129
213
|
}
|
|
130
214
|
|
|
131
215
|
const reasons = [];
|
|
216
|
+
const blockedIdentitySignals = collectBlockedIdentitySignals(worktreePath);
|
|
217
|
+
if (blockedIdentitySignals.length > 0) {
|
|
218
|
+
reasons.push(`blocked test git identity detected (${blockedIdentitySignals.join(", ")})`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const fixtureSignature = detectKnownFixtureSignature(worktreePath);
|
|
222
|
+
if (fixtureSignature) {
|
|
223
|
+
reasons.push(
|
|
224
|
+
`HEAD matches known test fixture signature (${fixtureSignature.sentinels.join(", ")} in ${fixtureSignature.trackedFiles} tracked files)`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
132
228
|
if (baseFiles >= 500 && headFiles <= Math.max(25, Math.floor(baseFiles * 0.15))) {
|
|
133
229
|
reasons.push(`HEAD tracks only ${headFiles}/${baseFiles} files vs ${remoteRef}`);
|
|
134
230
|
}
|
|
@@ -25,6 +25,7 @@ import { resolveCodexProfileRuntime } from "../shell/codex-model-profiles.mjs";
|
|
|
25
25
|
import {
|
|
26
26
|
evaluateBranchSafetyForPush,
|
|
27
27
|
normalizeBaseBranch,
|
|
28
|
+
sanitizeGitEnv,
|
|
28
29
|
} from "./git-safety.mjs";
|
|
29
30
|
|
|
30
31
|
// ── Configuration ────────────────────────────────────────────────────────────
|
|
@@ -73,6 +74,7 @@ function gitExec(args, cwd, timeoutMs = 30_000) {
|
|
|
73
74
|
stdio: ["ignore", "pipe", "pipe"],
|
|
74
75
|
shell: false,
|
|
75
76
|
timeout: timeoutMs,
|
|
77
|
+
env: sanitizeGitEnv(),
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
let stdout = "";
|
package/infra/monitor.mjs
CHANGED
|
@@ -14742,7 +14742,10 @@ injectMonitorFunctions({
|
|
|
14742
14742
|
},
|
|
14743
14743
|
triggerTaskPlanner,
|
|
14744
14744
|
});
|
|
14745
|
-
|
|
14745
|
+
const portalWantsStart =
|
|
14746
|
+
["1", "true", "yes"].includes(String(process.env.TELEGRAM_MINIAPP_ENABLED || "").toLowerCase()) ||
|
|
14747
|
+
Number(process.env.TELEGRAM_UI_PORT || "0") > 0;
|
|
14748
|
+
if (telegramBotEnabled || portalWantsStart) {
|
|
14746
14749
|
runDetached("telegram-bot:start-startup", () =>
|
|
14747
14750
|
startTelegramBot(getTelegramBotStartOptions()));
|
|
14748
14751
|
|
|
@@ -734,6 +734,11 @@ function Ensure-GitIdentity {
|
|
|
734
734
|
$email = Get-EnvFallback -Name "VE_GIT_AUTHOR_EMAIL"
|
|
735
735
|
if (-not $email) { $email = Get-EnvFallback -Name "GIT_AUTHOR_EMAIL" }
|
|
736
736
|
|
|
737
|
+
$blockedEmails = @("test@example.com", "bosun-tests@example.com")
|
|
738
|
+
if ($email -and ($blockedEmails -contains $email.ToLowerInvariant())) {
|
|
739
|
+
throw "Refusing to configure test git identity in live orchestrator environment: $email"
|
|
740
|
+
}
|
|
741
|
+
|
|
737
742
|
if ($name) {
|
|
738
743
|
try { git config user.name $name | Out-Null } catch { }
|
|
739
744
|
Set-EnvValue -Name "GIT_AUTHOR_NAME" -Value $name
|
|
@@ -17,11 +17,15 @@
|
|
|
17
17
|
* - Only resolves for successfully completed tasks
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { spawn, execSync } from "node:child_process";
|
|
20
|
+
import { spawn, spawnSync, execSync } from "node:child_process";
|
|
21
21
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
22
22
|
import { resolve } from "node:path";
|
|
23
23
|
import { fileURLToPath } from "url";
|
|
24
24
|
import { getWorktreeManager } from "../workspace/worktree-manager.mjs";
|
|
25
|
+
import {
|
|
26
|
+
evaluateBranchSafetyForPush,
|
|
27
|
+
sanitizeGitEnv,
|
|
28
|
+
} from "../git/git-safety.mjs";
|
|
25
29
|
|
|
26
30
|
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
27
31
|
|
|
@@ -65,6 +69,31 @@ function extractBranch(logLine) {
|
|
|
65
69
|
return branchMatch ? branchMatch[0] : null;
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
function runGit(args, cwd, opts = {}) {
|
|
73
|
+
const result = spawnSync("git", args, {
|
|
74
|
+
cwd,
|
|
75
|
+
encoding: "utf8",
|
|
76
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
77
|
+
shell: false,
|
|
78
|
+
timeout: opts.timeout ?? 30_000,
|
|
79
|
+
env: sanitizeGitEnv(process.env, opts.env || {}),
|
|
80
|
+
});
|
|
81
|
+
if (result.status === 0) {
|
|
82
|
+
return String(result.stdout || "").trim();
|
|
83
|
+
}
|
|
84
|
+
const stderr = String(result.stderr || result.stdout || result.error?.message || "git command failed").trim();
|
|
85
|
+
throw new Error(stderr || "git command failed");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function assertPushSafe(worktreePath, branch) {
|
|
89
|
+
const safety = evaluateBranchSafetyForPush(worktreePath, { baseBranch: "main" });
|
|
90
|
+
if (!safety.safe) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`refusing git mutation on ${branch}: ${safety.reason || "branch safety check failed"}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
68
97
|
// ── State Management ─────────────────────────────────────────────────────────
|
|
69
98
|
|
|
70
99
|
class ResolutionState {
|
|
@@ -196,26 +225,22 @@ class UncommittedChangesResolver {
|
|
|
196
225
|
}
|
|
197
226
|
|
|
198
227
|
try {
|
|
228
|
+
assertPushSafe(worktreePath, branch);
|
|
229
|
+
|
|
199
230
|
// Add uncommitted files
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
stdio: "pipe",
|
|
203
|
-
env: { ...process.env, GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
|
|
231
|
+
runGit(["add", "."], worktreePath, {
|
|
232
|
+
env: { GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
|
|
204
233
|
});
|
|
205
234
|
|
|
206
235
|
// Commit changes
|
|
207
236
|
const commitMsg = "chore(bosun): add uncommitted changes";
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
stdio: "pipe",
|
|
211
|
-
env: { ...process.env, GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
|
|
237
|
+
runGit(["commit", "-m", commitMsg, "--no-edit"], worktreePath, {
|
|
238
|
+
env: { GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
|
|
212
239
|
});
|
|
213
240
|
|
|
214
241
|
// Push to remote
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
stdio: "pipe",
|
|
218
|
-
});
|
|
242
|
+
assertPushSafe(worktreePath, branch);
|
|
243
|
+
runGit(["push", "origin", branch], worktreePath);
|
|
219
244
|
|
|
220
245
|
console.log(
|
|
221
246
|
`[vk-error-resolver] ✓ Resolved uncommitted changes on ${branch}`,
|
|
@@ -255,28 +280,19 @@ class PushFailureResolver {
|
|
|
255
280
|
|
|
256
281
|
try {
|
|
257
282
|
// Fetch latest from remote
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
stdio: "pipe",
|
|
261
|
-
});
|
|
283
|
+
assertPushSafe(worktreePath, branch);
|
|
284
|
+
runGit(["fetch", "origin", branch], worktreePath);
|
|
262
285
|
|
|
263
286
|
// Check if behind
|
|
264
|
-
const behind =
|
|
265
|
-
cwd: worktreePath,
|
|
266
|
-
encoding: "utf8",
|
|
267
|
-
}).trim();
|
|
287
|
+
const behind = runGit(["rev-list", "--count", `HEAD..origin/${branch}`], worktreePath);
|
|
268
288
|
|
|
269
289
|
if (parseInt(behind) > 0) {
|
|
270
290
|
// Rebase and retry push
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
stdio: "pipe",
|
|
274
|
-
env: { ...process.env, GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
|
|
275
|
-
});
|
|
276
|
-
execSync(`git push origin ${branch} --force-with-lease`, {
|
|
277
|
-
cwd: worktreePath,
|
|
278
|
-
stdio: "pipe",
|
|
291
|
+
runGit(["rebase", `origin/${branch}`], worktreePath, {
|
|
292
|
+
env: { GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
|
|
279
293
|
});
|
|
294
|
+
assertPushSafe(worktreePath, branch);
|
|
295
|
+
runGit(["push", "origin", branch, "--force-with-lease"], worktreePath);
|
|
280
296
|
|
|
281
297
|
console.log(
|
|
282
298
|
`[vk-error-resolver] ✓ Resolved push failure on ${branch} (rebased)`,
|
|
@@ -286,10 +302,8 @@ class PushFailureResolver {
|
|
|
286
302
|
}
|
|
287
303
|
|
|
288
304
|
// Try force push as last resort
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
stdio: "pipe",
|
|
292
|
-
});
|
|
305
|
+
assertPushSafe(worktreePath, branch);
|
|
306
|
+
runGit(["push", "origin", branch, "--force-with-lease"], worktreePath);
|
|
293
307
|
|
|
294
308
|
console.log(
|
|
295
309
|
`[vk-error-resolver] ✓ Resolved push failure on ${branch} (force-pushed)`,
|
|
@@ -345,22 +359,12 @@ class CIRetriggerResolver {
|
|
|
345
359
|
}
|
|
346
360
|
|
|
347
361
|
try {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
{
|
|
351
|
-
cwd: worktreePath,
|
|
352
|
-
stdio: "pipe",
|
|
353
|
-
env: {
|
|
354
|
-
...process.env,
|
|
355
|
-
GIT_EDITOR: ":",
|
|
356
|
-
GIT_MERGE_AUTOEDIT: "no",
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
);
|
|
360
|
-
execSync(`git push origin ${branch}`, {
|
|
361
|
-
cwd: worktreePath,
|
|
362
|
-
stdio: "pipe",
|
|
362
|
+
assertPushSafe(worktreePath, branch);
|
|
363
|
+
runGit(["commit", "--allow-empty", "-m", "chore: trigger CI", "--no-edit"], worktreePath, {
|
|
364
|
+
env: { GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
|
|
363
365
|
});
|
|
366
|
+
assertPushSafe(worktreePath, branch);
|
|
367
|
+
runGit(["push", "origin", branch], worktreePath);
|
|
364
368
|
|
|
365
369
|
console.log(`[vk-error-resolver] ✓ Triggered CI for PR #${prNumber}`);
|
|
366
370
|
this.stateManager.clearSignature(signature);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.10",
|
|
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",
|
|
@@ -350,4 +350,3 @@
|
|
|
350
350
|
"gaxios": "7.1.4"
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
|
-
|
package/server/ui-server.mjs
CHANGED
|
@@ -12598,7 +12598,7 @@ async function handleApi(req, res, url) {
|
|
|
12598
12598
|
const engine = wfCtx.engine;
|
|
12599
12599
|
const rawLimit = Number(url.searchParams.get("limit"));
|
|
12600
12600
|
const limit = Number.isFinite(rawLimit) && rawLimit > 0
|
|
12601
|
-
? Math.min(rawLimit,
|
|
12601
|
+
? Math.min(rawLimit, 5000)
|
|
12602
12602
|
: 200;
|
|
12603
12603
|
const runs = engine.getRunHistory ? engine.getRunHistory(null, limit) : [];
|
|
12604
12604
|
jsonResponse(res, 200, { ok: true, runs });
|
|
@@ -12772,7 +12772,7 @@ async function handleApi(req, res, url) {
|
|
|
12772
12772
|
if (action === "runs") {
|
|
12773
12773
|
const rawLimit = Number(url.searchParams.get("limit"));
|
|
12774
12774
|
const limit = Number.isFinite(rawLimit) && rawLimit > 0
|
|
12775
|
-
? Math.min(rawLimit,
|
|
12775
|
+
? Math.min(rawLimit, 5000)
|
|
12776
12776
|
: 200;
|
|
12777
12777
|
const runs = engine.getRunHistory ? engine.getRunHistory(workflowId, limit) : [];
|
|
12778
12778
|
jsonResponse(res, 200, { ok: true, runs });
|
|
@@ -16514,3 +16514,4 @@ export { getLocalLanIp };
|
|
|
16514
16514
|
|
|
16515
16515
|
|
|
16516
16516
|
|
|
16517
|
+
|
package/setup.mjs
CHANGED
|
@@ -980,6 +980,11 @@ function getGitHubAuthScopes(cwd) {
|
|
|
980
980
|
encoding: "utf8",
|
|
981
981
|
cwd: cwd || process.cwd(),
|
|
982
982
|
stdio: ["ignore", "pipe", "pipe"],
|
|
983
|
+
timeout: 3000,
|
|
984
|
+
env: {
|
|
985
|
+
...process.env,
|
|
986
|
+
GH_PROMPT_DISABLED: "1",
|
|
987
|
+
},
|
|
983
988
|
});
|
|
984
989
|
const line = String(output || "")
|
|
985
990
|
.split(/\r?\n/)
|
package/task/task-executor.mjs
CHANGED
|
@@ -83,7 +83,11 @@ import {
|
|
|
83
83
|
formatComplexityDecision,
|
|
84
84
|
normalizeExecutorKey,
|
|
85
85
|
} from "./task-complexity.mjs";
|
|
86
|
-
import {
|
|
86
|
+
import {
|
|
87
|
+
evaluateBranchSafetyForPush,
|
|
88
|
+
normalizeBaseBranch,
|
|
89
|
+
sanitizeGitEnv,
|
|
90
|
+
} from "../git/git-safety.mjs";
|
|
87
91
|
import {
|
|
88
92
|
loadHooks,
|
|
89
93
|
registerBuiltinHooks,
|
|
@@ -960,6 +964,7 @@ function ensureBaseBranchAvailable(repoRoot, baseBranch, defaultTargetBranch) {
|
|
|
960
964
|
cwd: repoRoot,
|
|
961
965
|
encoding: "utf8",
|
|
962
966
|
timeout: 5000,
|
|
967
|
+
env: sanitizeGitEnv(),
|
|
963
968
|
});
|
|
964
969
|
if (localCheck.status === 0) {
|
|
965
970
|
return branch;
|
|
@@ -971,6 +976,7 @@ function ensureBaseBranchAvailable(repoRoot, baseBranch, defaultTargetBranch) {
|
|
|
971
976
|
cwd: repoRoot,
|
|
972
977
|
encoding: "utf8",
|
|
973
978
|
timeout: 8000,
|
|
979
|
+
env: sanitizeGitEnv(),
|
|
974
980
|
});
|
|
975
981
|
remoteExists = remoteLocalCheck.status === 0;
|
|
976
982
|
if (!remoteExists) {
|
|
@@ -979,11 +985,13 @@ function ensureBaseBranchAvailable(repoRoot, baseBranch, defaultTargetBranch) {
|
|
|
979
985
|
cwd: repoRoot,
|
|
980
986
|
encoding: "utf8",
|
|
981
987
|
timeout: 15000,
|
|
988
|
+
env: sanitizeGitEnv(),
|
|
982
989
|
});
|
|
983
990
|
const refreshedCheck = spawnSync("git", ["show-ref", "--verify", remoteHeadRef], {
|
|
984
991
|
cwd: repoRoot,
|
|
985
992
|
encoding: "utf8",
|
|
986
993
|
timeout: 8000,
|
|
994
|
+
env: sanitizeGitEnv(),
|
|
987
995
|
});
|
|
988
996
|
remoteExists = refreshedCheck.status === 0;
|
|
989
997
|
} catch {
|
|
@@ -996,6 +1004,7 @@ function ensureBaseBranchAvailable(repoRoot, baseBranch, defaultTargetBranch) {
|
|
|
996
1004
|
cwd: repoRoot,
|
|
997
1005
|
encoding: "utf8",
|
|
998
1006
|
timeout: 8000,
|
|
1007
|
+
env: sanitizeGitEnv(),
|
|
999
1008
|
});
|
|
1000
1009
|
return branch;
|
|
1001
1010
|
}
|
|
@@ -1006,6 +1015,7 @@ function ensureBaseBranchAvailable(repoRoot, baseBranch, defaultTargetBranch) {
|
|
|
1006
1015
|
cwd: repoRoot,
|
|
1007
1016
|
encoding: "utf8",
|
|
1008
1017
|
timeout: 15000,
|
|
1018
|
+
env: sanitizeGitEnv(),
|
|
1009
1019
|
});
|
|
1010
1020
|
} catch {
|
|
1011
1021
|
/* best-effort */
|
|
@@ -1014,7 +1024,7 @@ function ensureBaseBranchAvailable(repoRoot, baseBranch, defaultTargetBranch) {
|
|
|
1014
1024
|
const createRes = spawnSync(
|
|
1015
1025
|
"git",
|
|
1016
1026
|
["branch", branch, fallbackNorm.remoteRef],
|
|
1017
|
-
{ cwd: repoRoot, encoding: "utf8", timeout: 8000 },
|
|
1027
|
+
{ cwd: repoRoot, encoding: "utf8", timeout: 8000, env: sanitizeGitEnv() },
|
|
1018
1028
|
);
|
|
1019
1029
|
if (createRes.status !== 0) {
|
|
1020
1030
|
const stderr = (createRes.stderr || "").trim();
|
|
@@ -1028,6 +1038,7 @@ function ensureBaseBranchAvailable(repoRoot, baseBranch, defaultTargetBranch) {
|
|
|
1028
1038
|
cwd: repoRoot,
|
|
1029
1039
|
encoding: "utf8",
|
|
1030
1040
|
timeout: 30_000,
|
|
1041
|
+
env: sanitizeGitEnv(),
|
|
1031
1042
|
});
|
|
1032
1043
|
return branch;
|
|
1033
1044
|
}
|
|
@@ -11136,34 +11136,16 @@ function stopBatchFlushLoop() {
|
|
|
11136
11136
|
*/
|
|
11137
11137
|
export async function startTelegramBot(options = {}) {
|
|
11138
11138
|
refreshTelegramConfigFromEnv();
|
|
11139
|
-
if (!telegramToken || !telegramChatId) {
|
|
11140
|
-
console.warn(
|
|
11141
|
-
"[telegram-bot] disabled (missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID)",
|
|
11142
|
-
);
|
|
11143
|
-
return;
|
|
11144
|
-
}
|
|
11145
|
-
|
|
11146
|
-
// Initialize the primary agent context
|
|
11147
|
-
await initPrimaryAgent();
|
|
11148
|
-
|
|
11149
|
-
// Probe Telegram API connectivity before startup registration
|
|
11150
|
-
const reachable = await probeTelegramConnectivity();
|
|
11151
|
-
if (reachable) {
|
|
11152
|
-
await registerBotCommands();
|
|
11153
|
-
} else {
|
|
11154
|
-
console.warn(
|
|
11155
|
-
"[telegram-bot] Telegram API unreachable at startup — command registration deferred",
|
|
11156
|
-
);
|
|
11157
|
-
}
|
|
11158
11139
|
|
|
11159
|
-
// Start Telegram UI server (Mini App) when configured.
|
|
11140
|
+
// Start Telegram UI server (Mini App / Portal) when configured.
|
|
11160
11141
|
// Portal startup is independent of Telegram polling state — it must always
|
|
11161
11142
|
// run when TELEGRAM_UI_PORT or TELEGRAM_MINIAPP_ENABLED is set, even when
|
|
11162
|
-
//
|
|
11143
|
+
// no Telegram bot token is configured (local-only portal mode).
|
|
11163
11144
|
const miniAppEnabled = ["1", "true", "yes"].includes(
|
|
11164
11145
|
String(process.env.TELEGRAM_MINIAPP_ENABLED || "").toLowerCase(),
|
|
11165
11146
|
);
|
|
11166
11147
|
const miniAppPort = Number(process.env.TELEGRAM_UI_PORT || "0");
|
|
11148
|
+
const hasTelegram = !!(telegramToken && telegramChatId);
|
|
11167
11149
|
|
|
11168
11150
|
if (miniAppEnabled || miniAppPort > 0) {
|
|
11169
11151
|
const restartReason = String(
|
|
@@ -11198,6 +11180,35 @@ export async function startTelegramBot(options = {}) {
|
|
|
11198
11180
|
},
|
|
11199
11181
|
});
|
|
11200
11182
|
syncUiUrlsFromServer();
|
|
11183
|
+
} catch (err) {
|
|
11184
|
+
console.warn(`[telegram-bot] UI server start failed: ${err.message}`);
|
|
11185
|
+
}
|
|
11186
|
+
}
|
|
11187
|
+
|
|
11188
|
+
if (!hasTelegram) {
|
|
11189
|
+
console.warn(
|
|
11190
|
+
"[telegram-bot] Telegram polling disabled (missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID)" +
|
|
11191
|
+
(miniAppEnabled || miniAppPort > 0 ? " — portal UI is still active" : ""),
|
|
11192
|
+
);
|
|
11193
|
+
return;
|
|
11194
|
+
}
|
|
11195
|
+
|
|
11196
|
+
// Initialize the primary agent context
|
|
11197
|
+
await initPrimaryAgent();
|
|
11198
|
+
|
|
11199
|
+
// Probe Telegram API connectivity before startup registration
|
|
11200
|
+
const reachable = await probeTelegramConnectivity();
|
|
11201
|
+
if (reachable) {
|
|
11202
|
+
await registerBotCommands();
|
|
11203
|
+
} else {
|
|
11204
|
+
console.warn(
|
|
11205
|
+
"[telegram-bot] Telegram API unreachable at startup — command registration deferred",
|
|
11206
|
+
);
|
|
11207
|
+
}
|
|
11208
|
+
|
|
11209
|
+
// Wire up Telegram-specific UI integrations (menu button, firewall alerts)
|
|
11210
|
+
if (miniAppEnabled || miniAppPort > 0) {
|
|
11211
|
+
try {
|
|
11201
11212
|
if (reachable && telegramWebAppUrl) {
|
|
11202
11213
|
const updated = await setWebAppMenuButton(telegramWebAppUrl);
|
|
11203
11214
|
if (updated) {
|
|
@@ -11269,7 +11280,7 @@ export async function startTelegramBot(options = {}) {
|
|
|
11269
11280
|
}
|
|
11270
11281
|
}
|
|
11271
11282
|
} catch (err) {
|
|
11272
|
-
console.warn(`[telegram-bot] UI
|
|
11283
|
+
console.warn(`[telegram-bot] UI Telegram integration failed: ${err.message}`);
|
|
11273
11284
|
if (reachable) {
|
|
11274
11285
|
await clearWebAppMenuButton();
|
|
11275
11286
|
lastMenuButtonUrl = null;
|