bosun 0.40.9 → 0.40.11
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/agent-work-analyzer.mjs +32 -9
- package/agent/fleet-coordinator.mjs +2 -6
- package/cli.mjs +17 -0
- package/git/git-safety.mjs +96 -0
- package/git/sdk-conflict-resolver.mjs +2 -0
- package/infra/library-manager.mjs +289 -70
- package/infra/monitor.mjs +382 -46
- package/kanban/kanban-adapter.mjs +29 -0
- package/kanban/ve-orchestrator.ps1 +5 -0
- package/kanban/vk-error-resolver.mjs +52 -48
- package/package.json +2 -3
- package/server/ui-server.mjs +174 -33
- package/setup.mjs +5 -0
- package/task/task-claims.mjs +99 -36
- package/task/task-cli.mjs +522 -2
- package/task/task-executor.mjs +795 -50
- package/task/task-store.mjs +88 -4
- package/telegram/telegram-bot.mjs +155 -54
- package/ui/app.js +16 -1
- package/ui/components/chat-view.js +7 -2
- package/ui/components/kanban-board.js +19 -10
- package/ui/components/session-list.js +82 -27
- package/ui/demo-defaults.js +555 -91
- package/ui/modules/session-api.js +1 -1
- package/ui/modules/state.js +6 -6
- package/ui/styles/components.css +170 -7
- package/ui/tabs/agents.js +247 -87
- package/ui/tabs/dashboard.js +399 -40
- package/ui/tabs/library.js +69 -6
- package/ui/tabs/tasks.js +255 -20
- package/ui/tabs/workflow-canvas-utils.mjs +182 -0
- package/ui/tabs/workflows.js +762 -218
- package/workflow/workflow-engine.mjs +132 -16
- package/workflow/workflow-nodes.mjs +467 -64
- package/workflow/workflow-templates.mjs +85 -1
- package/workflow-templates/agents.mjs +3 -2
- package/workflow-templates/github.mjs +3 -1
- package/workflow-templates/reliability.mjs +23 -12
- package/workflow-templates/task-batch.mjs +17 -3
- package/workflow-templates/task-lifecycle.mjs +84 -13
- 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
|
|
|
@@ -69,9 +69,25 @@ const ALERT_COOLDOWN_RETENTION_MS = Math.max(
|
|
|
69
69
|
FAILED_SESSION_TRANSIENT_ALERT_MIN_COOLDOWN_MS * 3,
|
|
70
70
|
3 * 60 * 60 * 1000,
|
|
71
71
|
); // keep cooldown history bounded
|
|
72
|
+
const ALERT_COOLDOWN_REPLAY_MIN_BYTES = 256 * 1024;
|
|
73
|
+
const ALERT_COOLDOWN_REPLAY_DEFAULT_MAX_BYTES = 8 * 1024 * 1024;
|
|
74
|
+
const ALERT_COOLDOWN_REPLAY_MAX_CAP_BYTES = 64 * 1024 * 1024;
|
|
75
|
+
|
|
76
|
+
function normalizeReplayMaxBytes(value) {
|
|
77
|
+
const parsed = Number(value);
|
|
78
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
79
|
+
return ALERT_COOLDOWN_REPLAY_DEFAULT_MAX_BYTES;
|
|
80
|
+
}
|
|
81
|
+
const rounded = Math.floor(parsed);
|
|
82
|
+
return Math.min(
|
|
83
|
+
ALERT_COOLDOWN_REPLAY_MAX_CAP_BYTES,
|
|
84
|
+
Math.max(ALERT_COOLDOWN_REPLAY_MIN_BYTES, rounded),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
const ALERT_COOLDOWN_REPLAY_MAX_BYTES = Math.max(
|
|
73
|
-
|
|
74
|
-
|
|
89
|
+
ALERT_COOLDOWN_REPLAY_MIN_BYTES,
|
|
90
|
+
normalizeReplayMaxBytes(process.env.AGENT_ALERT_COOLDOWN_REPLAY_MAX_BYTES),
|
|
75
91
|
);
|
|
76
92
|
|
|
77
93
|
function getAlertCooldownMs(alert) {
|
|
@@ -367,15 +383,19 @@ async function processLogFile(startPosition) {
|
|
|
367
383
|
* @param {Object} event - Parsed JSONL event
|
|
368
384
|
*/
|
|
369
385
|
async function analyzeEvent(event) {
|
|
370
|
-
const {
|
|
386
|
+
const { event_type, timestamp } = event;
|
|
387
|
+
const attemptId = String(event?.attempt_id || "").trim();
|
|
388
|
+
if (!attemptId) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
371
391
|
const parsedTs = Date.parse(timestamp);
|
|
372
392
|
const eventTime = Number.isFinite(parsedTs) ? parsedTs : Date.now();
|
|
373
393
|
const eventIso = new Date(eventTime).toISOString();
|
|
374
394
|
|
|
375
395
|
// Initialize session state if needed
|
|
376
|
-
if (!activeSessions.has(
|
|
377
|
-
activeSessions.set(
|
|
378
|
-
attempt_id,
|
|
396
|
+
if (!activeSessions.has(attemptId)) {
|
|
397
|
+
activeSessions.set(attemptId, {
|
|
398
|
+
attempt_id: attemptId,
|
|
379
399
|
errors: [],
|
|
380
400
|
toolCalls: [],
|
|
381
401
|
lastActivity: eventIso,
|
|
@@ -385,7 +405,7 @@ async function analyzeEvent(event) {
|
|
|
385
405
|
});
|
|
386
406
|
}
|
|
387
407
|
|
|
388
|
-
const session = activeSessions.get(
|
|
408
|
+
const session = activeSessions.get(attemptId);
|
|
389
409
|
session.lastActivity = eventIso;
|
|
390
410
|
|
|
391
411
|
// Route to specific analyzers
|
|
@@ -401,7 +421,7 @@ async function analyzeEvent(event) {
|
|
|
401
421
|
break;
|
|
402
422
|
case "session_end":
|
|
403
423
|
await analyzeSessionEnd(session, event);
|
|
404
|
-
activeSessions.delete(
|
|
424
|
+
activeSessions.delete(attemptId);
|
|
405
425
|
break;
|
|
406
426
|
}
|
|
407
427
|
|
|
@@ -647,7 +667,10 @@ async function emitAlert(alert) {
|
|
|
647
667
|
...alert,
|
|
648
668
|
};
|
|
649
669
|
|
|
650
|
-
|
|
670
|
+
const alertScope = String(
|
|
671
|
+
alert?.attempt_id || alert?.task_id || alert?.executor || "unknown",
|
|
672
|
+
);
|
|
673
|
+
console.error(`[ALERT] ${alert.type}: ${alertScope}`);
|
|
651
674
|
|
|
652
675
|
// Append to alerts log
|
|
653
676
|
try {
|
|
@@ -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/cli.mjs
CHANGED
|
@@ -76,6 +76,7 @@ function showHelp() {
|
|
|
76
76
|
bosun [options]
|
|
77
77
|
|
|
78
78
|
COMMANDS
|
|
79
|
+
audit <command> [options] Codebase annotation audit workflows (scan/generate/warn/manifest/index/trim/conformity/migrate)
|
|
79
80
|
--setup Launch the web-based setup wizard (default)
|
|
80
81
|
--setup-terminal Run the legacy terminal setup wizard
|
|
81
82
|
--where Show the resolved bosun config directory
|
|
@@ -1237,6 +1238,22 @@ async function main() {
|
|
|
1237
1238
|
process.exit(0);
|
|
1238
1239
|
}
|
|
1239
1240
|
|
|
1241
|
+
// Handle 'audit' subcommand before --help so command-specific help works.
|
|
1242
|
+
const auditFlagIndex = args.indexOf("--audit");
|
|
1243
|
+
const auditCommandIndex =
|
|
1244
|
+
args[0] === "audit"
|
|
1245
|
+
? 0
|
|
1246
|
+
: args[0]?.startsWith("--")
|
|
1247
|
+
? args.indexOf("audit")
|
|
1248
|
+
: -1;
|
|
1249
|
+
if (auditCommandIndex >= 0 || auditFlagIndex >= 0) {
|
|
1250
|
+
const { runAuditCli } = await import("./lib/codebase-audit.mjs");
|
|
1251
|
+
const commandStartIndex = auditCommandIndex >= 0 ? auditCommandIndex : auditFlagIndex;
|
|
1252
|
+
const auditArgs = args.slice(commandStartIndex + 1);
|
|
1253
|
+
const result = await runAuditCli(auditArgs);
|
|
1254
|
+
process.exit(Number.isInteger(result?.exitCode) ? result.exitCode : 0);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1240
1257
|
// Handle --help
|
|
1241
1258
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1242
1259
|
showHelp();
|
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 = "";
|