bosun 0.36.0 → 0.36.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/.env.example +98 -16
- package/README.md +27 -0
- package/agent-event-bus.mjs +5 -5
- package/agent-pool.mjs +129 -12
- package/agent-prompts.mjs +7 -1
- package/agent-sdk.mjs +13 -2
- package/agent-supervisor.mjs +2 -2
- package/agent-work-report.mjs +1 -1
- package/anomaly-detector.mjs +6 -6
- package/autofix.mjs +15 -15
- package/bosun-skills.mjs +4 -4
- package/bosun.schema.json +160 -4
- package/claude-shell.mjs +11 -11
- package/cli.mjs +21 -21
- package/codex-config.mjs +19 -19
- package/codex-shell.mjs +180 -29
- package/config-doctor.mjs +27 -2
- package/config.mjs +60 -7
- package/copilot-shell.mjs +4 -4
- package/error-detector.mjs +1 -1
- package/fleet-coordinator.mjs +2 -2
- package/gemini-shell.mjs +692 -0
- package/github-oauth-portal.mjs +1 -1
- package/github-reconciler.mjs +2 -2
- package/kanban-adapter.mjs +741 -168
- package/merge-strategy.mjs +25 -25
- package/monitor.mjs +123 -105
- package/opencode-shell.mjs +22 -22
- package/package.json +7 -1
- package/postinstall.mjs +22 -22
- package/pr-cleanup-daemon.mjs +6 -6
- package/prepublish-check.mjs +4 -4
- package/presence.mjs +2 -2
- package/primary-agent.mjs +85 -7
- package/publish.mjs +1 -1
- package/review-agent.mjs +1 -1
- package/session-tracker.mjs +11 -0
- package/setup-web-server.mjs +429 -21
- package/setup.mjs +367 -12
- package/shared-knowledge.mjs +1 -1
- package/startup-service.mjs +9 -9
- package/stream-resilience.mjs +58 -4
- package/sync-engine.mjs +2 -2
- package/task-assessment.mjs +9 -9
- package/task-cli.mjs +1 -1
- package/task-complexity.mjs +71 -2
- package/task-context.mjs +1 -2
- package/task-executor.mjs +104 -41
- package/telegram-bot.mjs +825 -494
- package/telegram-sentinel.mjs +28 -28
- package/ui/app.js +256 -23
- package/ui/app.monolith.js +1 -1
- package/ui/components/agent-selector.js +4 -3
- package/ui/components/chat-view.js +101 -28
- package/ui/components/diff-viewer.js +3 -3
- package/ui/components/kanban-board.js +3 -3
- package/ui/components/session-list.js +255 -35
- package/ui/components/workspace-switcher.js +3 -3
- package/ui/demo.html +209 -194
- package/ui/index.html +3 -3
- package/ui/modules/icon-utils.js +206 -142
- package/ui/modules/icons.js +2 -27
- package/ui/modules/settings-schema.js +29 -5
- package/ui/modules/streaming.js +30 -2
- package/ui/modules/vision-stream.js +275 -0
- package/ui/modules/voice-client.js +102 -9
- package/ui/modules/voice-fallback.js +62 -6
- package/ui/modules/voice-overlay.js +594 -59
- package/ui/modules/voice.js +31 -38
- package/ui/setup.html +284 -34
- package/ui/styles/components.css +47 -0
- package/ui/styles/sessions.css +75 -0
- package/ui/tabs/agents.js +73 -43
- package/ui/tabs/chat.js +37 -40
- package/ui/tabs/control.js +2 -2
- package/ui/tabs/dashboard.js +1 -1
- package/ui/tabs/infra.js +10 -10
- package/ui/tabs/library.js +8 -8
- package/ui/tabs/logs.js +10 -10
- package/ui/tabs/settings.js +20 -20
- package/ui/tabs/tasks.js +76 -47
- package/ui-server.mjs +1761 -124
- package/update-check.mjs +13 -13
- package/ve-kanban.mjs +1 -1
- package/whatsapp-channel.mjs +5 -5
- package/workflow-engine.mjs +20 -1
- package/workflow-nodes.mjs +904 -4
- package/workflow-templates/agents.mjs +321 -7
- package/workflow-templates/ci-cd.mjs +6 -6
- package/workflow-templates/github.mjs +156 -84
- package/workflow-templates/planning.mjs +8 -8
- package/workflow-templates/reliability.mjs +8 -8
- package/workflow-templates/security.mjs +3 -3
- package/workflow-templates.mjs +15 -9
- package/workspace-manager.mjs +85 -1
- package/workspace-monitor.mjs +2 -2
- package/workspace-registry.mjs +2 -2
- package/worktree-manager.mjs +1 -1
package/autofix.mjs
CHANGED
|
@@ -776,7 +776,7 @@ export async function attemptAutoFix(opts) {
|
|
|
776
776
|
);
|
|
777
777
|
if (onTelegram) {
|
|
778
778
|
onTelegram(
|
|
779
|
-
|
|
779
|
+
`:settings: Auto-fix gave up on raw crash (${reason}) after ${MAX_FIX_ATTEMPTS} attempts.\nManual intervention required.`,
|
|
780
780
|
);
|
|
781
781
|
}
|
|
782
782
|
return {
|
|
@@ -794,7 +794,7 @@ export async function attemptAutoFix(opts) {
|
|
|
794
794
|
|
|
795
795
|
if (onTelegram) {
|
|
796
796
|
onTelegram(
|
|
797
|
-
|
|
797
|
+
`:settings: Auto-fix starting [${modeLabel}] (raw fallback, attempt #${attemptNum}):\nCrash: ${reason}\nError indicators: ${fallback.errorLines.length} suspicious lines`,
|
|
798
798
|
);
|
|
799
799
|
}
|
|
800
800
|
|
|
@@ -830,7 +830,7 @@ export async function attemptAutoFix(opts) {
|
|
|
830
830
|
console.log("[autofix] npm mode — skipping execution, sending analysis");
|
|
831
831
|
|
|
832
832
|
const suggestion =
|
|
833
|
-
|
|
833
|
+
`:clipboard: *Auto-fix analysis* (raw fallback, attempt #${attemptNum}):\n` +
|
|
834
834
|
`Crash: ${reason}\n\n` +
|
|
835
835
|
`**Error indicators found:**\n` +
|
|
836
836
|
(fallback.errorLines.length > 0
|
|
@@ -889,7 +889,7 @@ export async function attemptAutoFix(opts) {
|
|
|
889
889
|
|
|
890
890
|
if (result.success && newChanges.length > 0) {
|
|
891
891
|
const outcomeMsg =
|
|
892
|
-
|
|
892
|
+
`:settings: Auto-fix applied (raw fallback, attempt #${attemptNum}):\n` +
|
|
893
893
|
`Crash: ${reason}\n` +
|
|
894
894
|
`Changes:\n${changeSummary}\n` +
|
|
895
895
|
`Codex SDK log: ${result.logPath}`;
|
|
@@ -904,7 +904,7 @@ export async function attemptAutoFix(opts) {
|
|
|
904
904
|
};
|
|
905
905
|
} else {
|
|
906
906
|
const outcomeMsg =
|
|
907
|
-
|
|
907
|
+
`:settings: Auto-fix fallback failed (attempt #${attemptNum}):\n` +
|
|
908
908
|
`Crash: ${reason}\n` +
|
|
909
909
|
`Codex: ${result.error || "no changes written"}\n` +
|
|
910
910
|
`Codex SDK log: ${result.logPath}`;
|
|
@@ -931,7 +931,7 @@ export async function attemptAutoFix(opts) {
|
|
|
931
931
|
.map((e) => `• ${e.errorType}: ${e.file}:${e.line}`)
|
|
932
932
|
.join("\n");
|
|
933
933
|
onTelegram(
|
|
934
|
-
|
|
934
|
+
`:settings: Auto-fix starting [${modeLabel}]:\nFound ${errors.length} error(s):\n${errorSummary}`,
|
|
935
935
|
);
|
|
936
936
|
}
|
|
937
937
|
|
|
@@ -950,7 +950,7 @@ export async function attemptAutoFix(opts) {
|
|
|
950
950
|
|
|
951
951
|
if (count >= MAX_FIX_ATTEMPTS && onTelegram) {
|
|
952
952
|
onTelegram(
|
|
953
|
-
|
|
953
|
+
`:settings: Auto-fix gave up on ${error.file}:${error.line} after ${MAX_FIX_ATTEMPTS} attempts.\n` +
|
|
954
954
|
`Error: ${error.message}\nManual intervention required.`,
|
|
955
955
|
);
|
|
956
956
|
}
|
|
@@ -1004,7 +1004,7 @@ export async function attemptAutoFix(opts) {
|
|
|
1004
1004
|
// ── NPM mode: analyze only, suggest fix to user ──────────────────
|
|
1005
1005
|
if (!devMode) {
|
|
1006
1006
|
const suggestion =
|
|
1007
|
-
|
|
1007
|
+
`:clipboard: *Auto-fix analysis* (attempt #${attemptNum}):\n` +
|
|
1008
1008
|
`**${error.errorType}** at \`${error.file}:${error.line}\`\n` +
|
|
1009
1009
|
`Message: ${error.message}\n` +
|
|
1010
1010
|
(error.codeLine ? `Failing code: \`${error.codeLine}\`\n` : "") +
|
|
@@ -1058,7 +1058,7 @@ export async function attemptAutoFix(opts) {
|
|
|
1058
1058
|
|
|
1059
1059
|
if (result.success) {
|
|
1060
1060
|
const outcomeMsg =
|
|
1061
|
-
|
|
1061
|
+
`:settings: Auto-fix applied (attempt #${attemptNum}):\n` +
|
|
1062
1062
|
`${error.errorType} at ${error.file}:${error.line}\n` +
|
|
1063
1063
|
`"${error.message}"\n` +
|
|
1064
1064
|
`Changes:\n${changeSummary}`;
|
|
@@ -1072,7 +1072,7 @@ export async function attemptAutoFix(opts) {
|
|
|
1072
1072
|
if (onTelegram) onTelegram(outcomeMsg);
|
|
1073
1073
|
} else {
|
|
1074
1074
|
const outcomeMsg =
|
|
1075
|
-
|
|
1075
|
+
`:settings: Auto-fix failed (attempt #${attemptNum}):\n` +
|
|
1076
1076
|
`${error.errorType} at ${error.file}:${error.line}\n` +
|
|
1077
1077
|
`Codex: ${result.error || "no changes written"}`;
|
|
1078
1078
|
|
|
@@ -1235,7 +1235,7 @@ export async function fixLoopingError(opts) {
|
|
|
1235
1235
|
|
|
1236
1236
|
if (!canAttemptFix(signature)) {
|
|
1237
1237
|
const count = getFixAttemptCount(signature);
|
|
1238
|
-
const outcome =
|
|
1238
|
+
const outcome = `:repeat: Loop fix gave up on repeating error after ${count} attempts.\n"${errorLine.slice(0, 200)}"\nManual intervention required.`;
|
|
1239
1239
|
console.warn(`[autofix] loop fix exhausted for: ${errorLine.slice(0, 80)}`);
|
|
1240
1240
|
if (onTelegram) onTelegram(outcome);
|
|
1241
1241
|
return { fixed: false, outcome };
|
|
@@ -1248,7 +1248,7 @@ export async function fixLoopingError(opts) {
|
|
|
1248
1248
|
|
|
1249
1249
|
if (onTelegram) {
|
|
1250
1250
|
onTelegram(
|
|
1251
|
-
|
|
1251
|
+
`:repeat: Repeating error detected [${modeLabel}] (${repeatCount}x, fix attempt #${attemptNum}):\n"${errorLine.slice(0, 200)}"`,
|
|
1252
1252
|
);
|
|
1253
1253
|
}
|
|
1254
1254
|
|
|
@@ -1312,7 +1312,7 @@ ${messagesCtx}
|
|
|
1312
1312
|
console.log("[autofix] npm mode — loop fix: analysis only");
|
|
1313
1313
|
|
|
1314
1314
|
const suggestion =
|
|
1315
|
-
|
|
1315
|
+
`:clipboard: *Loop fix analysis* (attempt #${attemptNum}):\n` +
|
|
1316
1316
|
`**Repeating error** (${repeatCount}x):\n` +
|
|
1317
1317
|
`\`${errorLine.slice(0, 300)}\`\n\n` +
|
|
1318
1318
|
`**Likely cause:** This error is repeating in a loop, likely because:\n` +
|
|
@@ -1359,7 +1359,7 @@ ${messagesCtx}
|
|
|
1359
1359
|
|
|
1360
1360
|
if (result.success && newChanges.length > 0) {
|
|
1361
1361
|
const outcome =
|
|
1362
|
-
|
|
1362
|
+
`:repeat: Loop fix applied (attempt #${attemptNum}):\n` +
|
|
1363
1363
|
`Error: "${errorLine.slice(0, 150)}"\n` +
|
|
1364
1364
|
`Changes:\n${changeSummary}`;
|
|
1365
1365
|
console.log(`[autofix] loop fix applied: ${newChanges.join(", ")}`);
|
|
@@ -1367,7 +1367,7 @@ ${messagesCtx}
|
|
|
1367
1367
|
return { fixed: true, outcome };
|
|
1368
1368
|
} else {
|
|
1369
1369
|
const outcome =
|
|
1370
|
-
|
|
1370
|
+
`:repeat: Loop fix failed (attempt #${attemptNum}):\n` +
|
|
1371
1371
|
`Error: "${errorLine.slice(0, 150)}"\n` +
|
|
1372
1372
|
`Codex: ${result.error || "no changes written"}`;
|
|
1373
1373
|
console.warn(`[autofix] loop fix codex exec failed: ${result.error}`);
|
package/bosun-skills.mjs
CHANGED
|
@@ -375,10 +375,10 @@ The scope is the **module or area** affected:
|
|
|
375
375
|
|
|
376
376
|
## Common Mistakes to Avoid
|
|
377
377
|
|
|
378
|
-
-
|
|
379
|
-
-
|
|
380
|
-
-
|
|
381
|
-
-
|
|
378
|
+
- :close: \`git commit -m "fix stuff"\` — too vague
|
|
379
|
+
- :close: \`git commit -m "WIP: not done yet"\` — commit only complete, testable units
|
|
380
|
+
- :close: \`git commit -am "…"\` — stages all tracked changes indiscriminately
|
|
381
|
+
- :check: \`git add src/auth/login.ts && git commit -m "fix(auth): handle empty token gracefully"\`
|
|
382
382
|
`,
|
|
383
383
|
},
|
|
384
384
|
{
|
package/bosun.schema.json
CHANGED
|
@@ -38,7 +38,98 @@
|
|
|
38
38
|
"codexEnabled": { "type": "boolean" },
|
|
39
39
|
"primaryAgent": {
|
|
40
40
|
"type": "string",
|
|
41
|
-
"enum": ["codex-sdk", "copilot-sdk", "claude-sdk", "opencode-sdk"]
|
|
41
|
+
"enum": ["codex-sdk", "copilot-sdk", "claude-sdk", "gemini-sdk", "opencode-sdk"]
|
|
42
|
+
},
|
|
43
|
+
"telegramUiTunnel": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"enum": ["named", "quick", "auto", "cloudflared", "disabled"],
|
|
46
|
+
"default": "named",
|
|
47
|
+
"description": "Mini App tunnel mode. named is the default permanent hostname mode."
|
|
48
|
+
},
|
|
49
|
+
"telegramUiAllowQuickTunnelFallback": {
|
|
50
|
+
"type": "boolean",
|
|
51
|
+
"default": false,
|
|
52
|
+
"description": "Allow quick trycloudflare fallback only when named tunnel startup fails."
|
|
53
|
+
},
|
|
54
|
+
"cloudflareTunnelName": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "Named Cloudflare tunnel identifier."
|
|
57
|
+
},
|
|
58
|
+
"cloudflareTunnelCredentials": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Path to cloudflared tunnel credentials JSON."
|
|
61
|
+
},
|
|
62
|
+
"cloudflareBaseDomain": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "Base domain used for deterministic per-user tunnel hostnames."
|
|
65
|
+
},
|
|
66
|
+
"cloudflareTunnelHostname": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "Explicit tunnel hostname override."
|
|
69
|
+
},
|
|
70
|
+
"cloudflareUsernameHostnamePolicy": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"enum": ["per-user-fixed", "fixed"],
|
|
73
|
+
"default": "per-user-fixed",
|
|
74
|
+
"description": "Policy for deriving named tunnel hostname from workstation identity."
|
|
75
|
+
},
|
|
76
|
+
"cloudflareZoneId": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"description": "Cloudflare zone ID used for DNS orchestration."
|
|
79
|
+
},
|
|
80
|
+
"cloudflareApiToken": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "Cloudflare API token scoped to Zone DNS edit."
|
|
83
|
+
},
|
|
84
|
+
"cloudflareDnsSyncEnabled": {
|
|
85
|
+
"type": "boolean",
|
|
86
|
+
"default": true,
|
|
87
|
+
"description": "Enable Cloudflare DNS idempotent create/verify/update during named tunnel startup."
|
|
88
|
+
},
|
|
89
|
+
"cloudflareDnsMaxRetries": {
|
|
90
|
+
"type": "number",
|
|
91
|
+
"default": 3,
|
|
92
|
+
"description": "Retry attempts for Cloudflare DNS API operations."
|
|
93
|
+
},
|
|
94
|
+
"cloudflareDnsRetryBaseMs": {
|
|
95
|
+
"type": "number",
|
|
96
|
+
"default": 750,
|
|
97
|
+
"description": "Base backoff delay for Cloudflare DNS API retries."
|
|
98
|
+
},
|
|
99
|
+
"telegramUiFallbackAuthEnabled": {
|
|
100
|
+
"type": "boolean",
|
|
101
|
+
"default": true,
|
|
102
|
+
"description": "Enable admin fallback PIN/password auth when Telegram/session auth is unavailable."
|
|
103
|
+
},
|
|
104
|
+
"telegramUiFallbackAuthRateLimitIpPerMin": {
|
|
105
|
+
"type": "number",
|
|
106
|
+
"default": 10,
|
|
107
|
+
"description": "Per-IP fallback auth attempt rate limit per minute."
|
|
108
|
+
},
|
|
109
|
+
"telegramUiFallbackAuthRateLimitGlobalPerMin": {
|
|
110
|
+
"type": "number",
|
|
111
|
+
"default": 60,
|
|
112
|
+
"description": "Global fallback auth attempt rate limit per minute."
|
|
113
|
+
},
|
|
114
|
+
"telegramUiFallbackAuthMaxFailures": {
|
|
115
|
+
"type": "number",
|
|
116
|
+
"default": 5,
|
|
117
|
+
"description": "Failed fallback auth attempts before temporary lockout."
|
|
118
|
+
},
|
|
119
|
+
"telegramUiFallbackAuthLockoutMs": {
|
|
120
|
+
"type": "number",
|
|
121
|
+
"default": 600000,
|
|
122
|
+
"description": "Temporary lockout duration in milliseconds after max fallback auth failures."
|
|
123
|
+
},
|
|
124
|
+
"telegramUiFallbackAuthRotateDays": {
|
|
125
|
+
"type": "number",
|
|
126
|
+
"default": 30,
|
|
127
|
+
"description": "Target credential rotation interval for fallback auth."
|
|
128
|
+
},
|
|
129
|
+
"telegramUiFallbackAuthTransientCooldownMs": {
|
|
130
|
+
"type": "number",
|
|
131
|
+
"default": 5000,
|
|
132
|
+
"description": "Cooldown when fallback auth backend has transient errors."
|
|
42
133
|
},
|
|
43
134
|
"voice": {
|
|
44
135
|
"type": "object",
|
|
@@ -52,15 +143,20 @@
|
|
|
52
143
|
},
|
|
53
144
|
"provider": {
|
|
54
145
|
"type": "string",
|
|
55
|
-
"enum": ["openai", "azure", "fallback", "auto"],
|
|
146
|
+
"enum": ["openai", "azure", "claude", "gemini", "fallback", "auto"],
|
|
56
147
|
"default": "auto",
|
|
57
|
-
"description": "Voice provider: openai (
|
|
148
|
+
"description": "Voice provider: openai/azure (Tier 1 realtime), claude/gemini (Tier 2 voice + provider vision), fallback (browser STT/TTS), auto (detect from env)"
|
|
58
149
|
},
|
|
59
150
|
"model": {
|
|
60
151
|
"type": "string",
|
|
61
152
|
"default": "gpt-4o-realtime-preview-2024-12-17",
|
|
62
153
|
"description": "Realtime API model name"
|
|
63
154
|
},
|
|
155
|
+
"visionModel": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"default": "gpt-4.1-mini",
|
|
158
|
+
"description": "Vision model used for live screen/camera frame analysis"
|
|
159
|
+
},
|
|
64
160
|
"openaiApiKey": {
|
|
65
161
|
"type": "string",
|
|
66
162
|
"description": "OpenAI API key for Realtime API (overrides OPENAI_API_KEY env)"
|
|
@@ -78,6 +174,14 @@
|
|
|
78
174
|
"default": "gpt-4o-realtime-preview",
|
|
79
175
|
"description": "Azure OpenAI deployment name"
|
|
80
176
|
},
|
|
177
|
+
"claudeApiKey": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"description": "Anthropic API key for Claude voice/vision provider mode"
|
|
180
|
+
},
|
|
181
|
+
"geminiApiKey": {
|
|
182
|
+
"type": "string",
|
|
183
|
+
"description": "Gemini API key for Gemini voice/vision provider mode"
|
|
184
|
+
},
|
|
81
185
|
"voiceId": {
|
|
82
186
|
"type": "string",
|
|
83
187
|
"enum": ["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"],
|
|
@@ -102,7 +206,7 @@
|
|
|
102
206
|
},
|
|
103
207
|
"delegateExecutor": {
|
|
104
208
|
"type": "string",
|
|
105
|
-
"enum": ["codex-sdk", "copilot-sdk", "claude-sdk", "opencode-sdk"],
|
|
209
|
+
"enum": ["codex-sdk", "copilot-sdk", "claude-sdk", "gemini-sdk", "opencode-sdk"],
|
|
106
210
|
"description": "Which executor to use for delegate_to_agent calls. Defaults to primaryAgent."
|
|
107
211
|
}
|
|
108
212
|
}
|
|
@@ -456,6 +560,58 @@
|
|
|
456
560
|
}
|
|
457
561
|
]
|
|
458
562
|
},
|
|
563
|
+
"internalExecutor": {
|
|
564
|
+
"type": "object",
|
|
565
|
+
"additionalProperties": true,
|
|
566
|
+
"properties": {
|
|
567
|
+
"mode": {
|
|
568
|
+
"type": "string",
|
|
569
|
+
"enum": ["vk", "internal", "hybrid"]
|
|
570
|
+
},
|
|
571
|
+
"maxParallel": { "type": "number" },
|
|
572
|
+
"baseBranchParallelLimit": { "type": "number" },
|
|
573
|
+
"pollIntervalMs": { "type": "number" },
|
|
574
|
+
"sdk": {
|
|
575
|
+
"type": "string",
|
|
576
|
+
"enum": ["auto", "codex", "copilot", "claude", "gemini", "opencode"]
|
|
577
|
+
},
|
|
578
|
+
"taskTimeoutMs": { "type": "number" },
|
|
579
|
+
"maxRetries": { "type": "number" },
|
|
580
|
+
"reviewAgentEnabled": { "type": "boolean" },
|
|
581
|
+
"reviewMaxConcurrent": { "type": "number" },
|
|
582
|
+
"reviewTimeoutMs": { "type": "number" },
|
|
583
|
+
"stream": {
|
|
584
|
+
"type": "object",
|
|
585
|
+
"additionalProperties": false,
|
|
586
|
+
"properties": {
|
|
587
|
+
"maxRetries": {
|
|
588
|
+
"type": "number",
|
|
589
|
+
"description": "Transient stream retry attempts per run"
|
|
590
|
+
},
|
|
591
|
+
"retryBaseMs": {
|
|
592
|
+
"type": "number",
|
|
593
|
+
"description": "Base stream retry delay in milliseconds"
|
|
594
|
+
},
|
|
595
|
+
"retryMaxMs": {
|
|
596
|
+
"type": "number",
|
|
597
|
+
"description": "Maximum stream retry delay in milliseconds"
|
|
598
|
+
},
|
|
599
|
+
"firstEventTimeoutMs": {
|
|
600
|
+
"type": "number",
|
|
601
|
+
"description": "Abort/retry if no stream events arrive within this timeout"
|
|
602
|
+
},
|
|
603
|
+
"maxItemsPerTurn": {
|
|
604
|
+
"type": "number",
|
|
605
|
+
"description": "Maximum completed stream items retained per turn"
|
|
606
|
+
},
|
|
607
|
+
"maxItemChars": {
|
|
608
|
+
"type": "number",
|
|
609
|
+
"description": "Maximum characters retained per string field in stored stream items"
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
},
|
|
459
615
|
"profiles": { "$ref": "#/$defs/profiles" },
|
|
460
616
|
"envProfiles": { "$ref": "#/$defs/profiles" },
|
|
461
617
|
"executors": {
|
package/claude-shell.mjs
CHANGED
|
@@ -105,27 +105,27 @@ function formatEvent(event) {
|
|
|
105
105
|
event.type === "item.started" &&
|
|
106
106
|
event.item?.type === "command_execution"
|
|
107
107
|
) {
|
|
108
|
-
return
|
|
108
|
+
return `:zap: Running: \`${event.item.command}\``;
|
|
109
109
|
}
|
|
110
110
|
if (
|
|
111
111
|
event.type === "item.completed" &&
|
|
112
112
|
event.item?.type === "command_execution"
|
|
113
113
|
) {
|
|
114
|
-
const status = event.item.exit_code === 0 ? "
|
|
114
|
+
const status = event.item.exit_code === 0 ? ":check:" : ":close:";
|
|
115
115
|
return `${status} Command done: \`${event.item.command}\``;
|
|
116
116
|
}
|
|
117
117
|
if (event.type === "item.started" && event.item?.type === "mcp_tool_call") {
|
|
118
|
-
return
|
|
118
|
+
return `:plug: MCP [${event.item.server}/${event.item.tool}]`;
|
|
119
119
|
}
|
|
120
120
|
if (event.type === "item.completed" && event.item?.type === "mcp_tool_call") {
|
|
121
|
-
const status = event.item.status === "completed" ? "
|
|
121
|
+
const status = event.item.status === "completed" ? ":check:" : ":close:";
|
|
122
122
|
return `${status} MCP [${event.item.server}/${event.item.tool}]`;
|
|
123
123
|
}
|
|
124
124
|
if (event.type === "item.started" && event.item?.type === "web_search") {
|
|
125
|
-
return
|
|
125
|
+
return `:search: Searching: ${event.item.query || ""}`;
|
|
126
126
|
}
|
|
127
127
|
if (event.type === "item.updated" && event.item?.type === "reasoning") {
|
|
128
|
-
return event.item.text ?
|
|
128
|
+
return event.item.text ? `:u1f4ad: ${event.item.text.slice(0, 300)}` : null;
|
|
129
129
|
}
|
|
130
130
|
return null;
|
|
131
131
|
}
|
|
@@ -496,7 +496,7 @@ export async function execClaudePrompt(userMessage, options = {}) {
|
|
|
496
496
|
if (activeTurn && !options._holdActiveTurn) {
|
|
497
497
|
return {
|
|
498
498
|
finalResponse:
|
|
499
|
-
"
|
|
499
|
+
":clock: Agent is still executing a previous task. Please wait.",
|
|
500
500
|
items: [],
|
|
501
501
|
usage: null,
|
|
502
502
|
};
|
|
@@ -505,7 +505,7 @@ export async function execClaudePrompt(userMessage, options = {}) {
|
|
|
505
505
|
const query = await loadClaudeSdk();
|
|
506
506
|
if (!query) {
|
|
507
507
|
return {
|
|
508
|
-
finalResponse: "
|
|
508
|
+
finalResponse: ":close: Claude SDK not available.",
|
|
509
509
|
items: [],
|
|
510
510
|
usage: null,
|
|
511
511
|
};
|
|
@@ -663,8 +663,8 @@ export async function execClaudePrompt(userMessage, options = {}) {
|
|
|
663
663
|
const reason = abortReason || controller.signal.reason;
|
|
664
664
|
const msg =
|
|
665
665
|
reason === "user_stop"
|
|
666
|
-
? "
|
|
667
|
-
:
|
|
666
|
+
? ":close: Agent stopped by user."
|
|
667
|
+
: `:clock: Agent timed out after ${timeoutMs / 1000}s`;
|
|
668
668
|
return { finalResponse: msg, items: [], usage: null };
|
|
669
669
|
}
|
|
670
670
|
// ── Transient stream retry ──────────────────────────────────────────────────
|
|
@@ -696,7 +696,7 @@ export async function execClaudePrompt(userMessage, options = {}) {
|
|
|
696
696
|
}
|
|
697
697
|
const message = err?.message || String(err || "unknown error");
|
|
698
698
|
return {
|
|
699
|
-
finalResponse:
|
|
699
|
+
finalResponse: `:close: Claude agent failed: ${message}`,
|
|
700
700
|
items: [],
|
|
701
701
|
usage: null,
|
|
702
702
|
};
|
package/cli.mjs
CHANGED
|
@@ -511,7 +511,7 @@ function startDaemon() {
|
|
|
511
511
|
// deleted the old codex-monitor directory and its PID file with it).
|
|
512
512
|
const ghosts = findGhostDaemonPids();
|
|
513
513
|
if (ghosts.length > 0) {
|
|
514
|
-
console.log(`
|
|
514
|
+
console.log(` :alert: Found ${ghosts.length} ghost bosun daemon process(es) with no PID file: ${ghosts.join(", ")}`);
|
|
515
515
|
console.log(` Stopping ghost process(es) before starting fresh...`);
|
|
516
516
|
for (const gpid of ghosts) {
|
|
517
517
|
try { process.kill(gpid, "SIGTERM"); } catch { /* already dead */ }
|
|
@@ -526,7 +526,7 @@ function startDaemon() {
|
|
|
526
526
|
for (const gpid of alive) {
|
|
527
527
|
try { process.kill(gpid, "SIGKILL"); } catch { /* ok */ }
|
|
528
528
|
}
|
|
529
|
-
console.log(`
|
|
529
|
+
console.log(` :check: Ghost process(es) stopped.`);
|
|
530
530
|
}
|
|
531
531
|
|
|
532
532
|
// Ensure log directory exists
|
|
@@ -673,7 +673,7 @@ function daemonStatus() {
|
|
|
673
673
|
// Check for ghost processes (alive but no PID file)
|
|
674
674
|
const ghosts = findGhostDaemonPids();
|
|
675
675
|
if (ghosts.length > 0) {
|
|
676
|
-
console.log(`
|
|
676
|
+
console.log(` :alert: bosun daemon is NOT tracked (no PID file), but ${ghosts.length} ghost process(es) found: ${ghosts.join(", ")}`);
|
|
677
677
|
console.log(` The daemon is likely running but its PID file was lost.`);
|
|
678
678
|
console.log(` Run --stop-daemon to clean up, then --daemon to restart.`);
|
|
679
679
|
} else {
|
|
@@ -793,7 +793,7 @@ function terminateBosun() {
|
|
|
793
793
|
const killed = allPids.length - alive.length;
|
|
794
794
|
console.log(` ✓ Terminated ${killed}/${allPids.length} process(es).`);
|
|
795
795
|
if (alive.length > 0) {
|
|
796
|
-
console.log(`
|
|
796
|
+
console.log(` :alert: Still alive: ${alive.join(", ")}`);
|
|
797
797
|
process.exit(1);
|
|
798
798
|
return;
|
|
799
799
|
}
|
|
@@ -828,13 +828,13 @@ async function main() {
|
|
|
828
828
|
await import("./desktop-shortcut.mjs");
|
|
829
829
|
const result = installDesktopShortcut();
|
|
830
830
|
if (result.success) {
|
|
831
|
-
console.log(`
|
|
831
|
+
console.log(` :check: Desktop shortcut installed (${result.method})`);
|
|
832
832
|
if (result.path) console.log(` Path: ${result.path}`);
|
|
833
833
|
if (result.name) console.log(` Name: ${result.name}`);
|
|
834
834
|
} else {
|
|
835
835
|
const method = getDesktopShortcutMethodName();
|
|
836
836
|
console.error(
|
|
837
|
-
`
|
|
837
|
+
` :close: Failed to install desktop shortcut (${method}): ${result.error}`,
|
|
838
838
|
);
|
|
839
839
|
}
|
|
840
840
|
process.exit(result.success ? 0 : 1);
|
|
@@ -843,10 +843,10 @@ async function main() {
|
|
|
843
843
|
const { removeDesktopShortcut } = await import("./desktop-shortcut.mjs");
|
|
844
844
|
const result = removeDesktopShortcut();
|
|
845
845
|
if (result.success) {
|
|
846
|
-
console.log(`
|
|
846
|
+
console.log(` :check: Desktop shortcut removed`);
|
|
847
847
|
} else {
|
|
848
848
|
console.error(
|
|
849
|
-
`
|
|
849
|
+
` :close: Failed to remove desktop shortcut: ${result.error}`,
|
|
850
850
|
);
|
|
851
851
|
}
|
|
852
852
|
process.exit(result.success ? 0 : 1);
|
|
@@ -907,7 +907,7 @@ async function main() {
|
|
|
907
907
|
const { shouldRunSetup, runSetup } = await import("./setup.mjs");
|
|
908
908
|
if (shouldRunSetup()) {
|
|
909
909
|
console.log(
|
|
910
|
-
"\n
|
|
910
|
+
"\n :rocket: First run detected — setup is required before daemon mode.\n",
|
|
911
911
|
);
|
|
912
912
|
await runSetup();
|
|
913
913
|
console.log("\n Setup complete. Starting daemon...\n");
|
|
@@ -1008,7 +1008,7 @@ async function main() {
|
|
|
1008
1008
|
process.env.BOSUN_SENTINEL_STRICT,
|
|
1009
1009
|
false,
|
|
1010
1010
|
);
|
|
1011
|
-
const prefix = strictSentinel ? "
|
|
1011
|
+
const prefix = strictSentinel ? ":close:" : ":alert:";
|
|
1012
1012
|
const suffix = strictSentinel
|
|
1013
1013
|
? ""
|
|
1014
1014
|
: " (continuing without sentinel companion)";
|
|
@@ -1235,7 +1235,7 @@ async function main() {
|
|
|
1235
1235
|
if (configDirArg) {
|
|
1236
1236
|
process.env.BOSUN_DIR = configDirArg;
|
|
1237
1237
|
}
|
|
1238
|
-
console.log("\n
|
|
1238
|
+
console.log("\n :rocket: First run detected — launching setup wizard...\n");
|
|
1239
1239
|
const { startSetupServer } = await import("./setup-web-server.mjs");
|
|
1240
1240
|
await startSetupServer();
|
|
1241
1241
|
console.log("\n Setup complete! Starting bosun...\n");
|
|
@@ -1246,16 +1246,16 @@ async function main() {
|
|
|
1246
1246
|
const legacyInfo = detectLegacySetup();
|
|
1247
1247
|
if (legacyInfo.hasLegacy && !legacyInfo.alreadyMigrated) {
|
|
1248
1248
|
console.log(
|
|
1249
|
-
`\n
|
|
1249
|
+
`\n :box: Detected legacy codex-monitor config at ${legacyInfo.legacyDir}`,
|
|
1250
1250
|
);
|
|
1251
1251
|
console.log(` Auto-migrating to ${legacyInfo.newDir}...\n`);
|
|
1252
1252
|
const result = migrateFromLegacy(legacyInfo.legacyDir, legacyInfo.newDir);
|
|
1253
1253
|
if (result.migrated.length > 0) {
|
|
1254
|
-
console.log(`
|
|
1254
|
+
console.log(` :check: Migrated: ${result.migrated.join(", ")}`);
|
|
1255
1255
|
console.log(`\n Config is now at ${legacyInfo.newDir}\n`);
|
|
1256
1256
|
}
|
|
1257
1257
|
for (const err of result.errors) {
|
|
1258
|
-
console.log(`
|
|
1258
|
+
console.log(` :alert: Migration warning: ${err}`);
|
|
1259
1259
|
}
|
|
1260
1260
|
}
|
|
1261
1261
|
|
|
@@ -1428,7 +1428,7 @@ async function sendCrashNotification(exitCode, signal, options = {}) {
|
|
|
1428
1428
|
.join("\n")
|
|
1429
1429
|
: "Monitor is no longer running. Manual restart required.";
|
|
1430
1430
|
const text =
|
|
1431
|
-
|
|
1431
|
+
`:zap: *CRASH* ${tag} bosun v${VERSION} died unexpectedly\n` +
|
|
1432
1432
|
`Host: \`${host}\`\n` +
|
|
1433
1433
|
`Reason: \`${reason}\`\n` +
|
|
1434
1434
|
`Time: ${new Date().toISOString()}\n\n` +
|
|
@@ -1604,7 +1604,7 @@ function runMonitor({ restartReason = "" } = {}) {
|
|
|
1604
1604
|
Math.round(crashState.instantCrashWindowMs / 1000),
|
|
1605
1605
|
);
|
|
1606
1606
|
console.error(
|
|
1607
|
-
`\n
|
|
1607
|
+
`\n :close: Monitor crashed too quickly ${crashState.instantCrashCount} times in a row (each <= ${windowSec}s, latest ${durationSec}s). Auto-restart is now paused.`,
|
|
1608
1608
|
);
|
|
1609
1609
|
sendCrashNotification(exitCode, signal).finally(() =>
|
|
1610
1610
|
process.exit(exitCode),
|
|
@@ -1617,7 +1617,7 @@ function runMonitor({ restartReason = "" } = {}) {
|
|
|
1617
1617
|
daemonRestartCount > DAEMON_MAX_RESTARTS
|
|
1618
1618
|
) {
|
|
1619
1619
|
console.error(
|
|
1620
|
-
`\n
|
|
1620
|
+
`\n :close: Monitor crashed too many times (${daemonRestartCount - 1} restarts, max ${DAEMON_MAX_RESTARTS}).`,
|
|
1621
1621
|
);
|
|
1622
1622
|
sendCrashNotification(exitCode, signal).finally(() =>
|
|
1623
1623
|
process.exit(exitCode),
|
|
@@ -1632,7 +1632,7 @@ function runMonitor({ restartReason = "" } = {}) {
|
|
|
1632
1632
|
? `${daemonRestartCount}/${DAEMON_MAX_RESTARTS}`
|
|
1633
1633
|
: `${daemonRestartCount}`;
|
|
1634
1634
|
console.error(
|
|
1635
|
-
`\n
|
|
1635
|
+
`\n :alert: Monitor exited (${reasonLabel}) — auto-restarting in ${Math.max(1, Math.round(delayMs / 1000))}s${IS_DAEMON_CHILD ? ` [attempt ${attemptLabel}]` : ""}...`,
|
|
1636
1636
|
);
|
|
1637
1637
|
sendCrashNotification(exitCode, signal, {
|
|
1638
1638
|
autoRestartInMs: delayMs,
|
|
@@ -1653,7 +1653,7 @@ function runMonitor({ restartReason = "" } = {}) {
|
|
|
1653
1653
|
|
|
1654
1654
|
if (exitCode !== 0 && !gracefulShutdown) {
|
|
1655
1655
|
console.error(
|
|
1656
|
-
`\n
|
|
1656
|
+
`\n :close: Monitor crashed (${signal ? `signal ${signal}` : `exit code ${exitCode}`}) — sending crash notification...`,
|
|
1657
1657
|
);
|
|
1658
1658
|
sendCrashNotification(exitCode, signal).finally(() =>
|
|
1659
1659
|
process.exit(exitCode),
|
|
@@ -1668,12 +1668,12 @@ function runMonitor({ restartReason = "" } = {}) {
|
|
|
1668
1668
|
|
|
1669
1669
|
monitorChild.on("error", (err) => {
|
|
1670
1670
|
monitorChild = null;
|
|
1671
|
-
console.error(`\n
|
|
1671
|
+
console.error(`\n :close: Monitor failed to start: ${err.message}`);
|
|
1672
1672
|
sendCrashNotification(1, null).finally(() => reject(err));
|
|
1673
1673
|
});
|
|
1674
1674
|
})
|
|
1675
1675
|
.catch((err) => {
|
|
1676
|
-
console.error(`\n
|
|
1676
|
+
console.error(`\n :close: Monitor failed to start: ${err.message}`);
|
|
1677
1677
|
sendCrashNotification(1, null).finally(() => reject(err));
|
|
1678
1678
|
});
|
|
1679
1679
|
});
|