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/setup.mjs
CHANGED
|
@@ -217,22 +217,22 @@ function headingStep(step, label, markProgress) {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
function check(label, ok, hint) {
|
|
220
|
-
const icon = ok ? "
|
|
220
|
+
const icon = ok ? ":check:" : ":close:";
|
|
221
221
|
console.log(` ${icon} ${label}`);
|
|
222
222
|
if (!ok && hint) console.log(` → ${hint}`);
|
|
223
223
|
return ok;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
function info(msg) {
|
|
227
|
-
console.log(`
|
|
227
|
+
console.log(` :help: ${msg}`);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
function success(msg) {
|
|
231
|
-
console.log(`
|
|
231
|
+
console.log(` :check: ${msg}`);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
function warn(msg) {
|
|
235
|
-
console.log(`
|
|
235
|
+
console.log(` :alert: ${msg}`);
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
function escapeTelegramHtml(value) {
|
|
@@ -917,6 +917,7 @@ function defaultVariantForExecutor(executor) {
|
|
|
917
917
|
if (normalized === "COPILOT" || normalized === "CLAUDE") {
|
|
918
918
|
return "CLAUDE_OPUS_4_6";
|
|
919
919
|
}
|
|
920
|
+
if (normalized === "GEMINI") return "DEFAULT";
|
|
920
921
|
return "DEFAULT";
|
|
921
922
|
}
|
|
922
923
|
|
|
@@ -1592,6 +1593,31 @@ const EXECUTOR_PRESETS = {
|
|
|
1592
1593
|
role: "primary",
|
|
1593
1594
|
},
|
|
1594
1595
|
],
|
|
1596
|
+
"gemini-only": [
|
|
1597
|
+
{
|
|
1598
|
+
name: "gemini-default",
|
|
1599
|
+
executor: "GEMINI",
|
|
1600
|
+
variant: "DEFAULT",
|
|
1601
|
+
weight: 100,
|
|
1602
|
+
role: "primary",
|
|
1603
|
+
},
|
|
1604
|
+
],
|
|
1605
|
+
"gemini-codex": [
|
|
1606
|
+
{
|
|
1607
|
+
name: "gemini-default",
|
|
1608
|
+
executor: "GEMINI",
|
|
1609
|
+
variant: "DEFAULT",
|
|
1610
|
+
weight: 60,
|
|
1611
|
+
role: "primary",
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
name: "codex-backup",
|
|
1615
|
+
executor: "CODEX",
|
|
1616
|
+
variant: "DEFAULT",
|
|
1617
|
+
weight: 40,
|
|
1618
|
+
role: "backup",
|
|
1619
|
+
},
|
|
1620
|
+
],
|
|
1595
1621
|
"opencode-only": [
|
|
1596
1622
|
{
|
|
1597
1623
|
name: "opencode-default",
|
|
@@ -1740,11 +1766,29 @@ function applyTelegramMiniAppDefaults(env, sourceEnv = process.env) {
|
|
|
1740
1766
|
);
|
|
1741
1767
|
|
|
1742
1768
|
if (!env.TELEGRAM_UI_TUNNEL && !sourceEnv.TELEGRAM_UI_TUNNEL) {
|
|
1743
|
-
env.TELEGRAM_UI_TUNNEL = "
|
|
1769
|
+
env.TELEGRAM_UI_TUNNEL = "named";
|
|
1744
1770
|
}
|
|
1745
1771
|
if (!env.TELEGRAM_UI_ALLOW_UNSAFE && !sourceEnv.TELEGRAM_UI_ALLOW_UNSAFE) {
|
|
1746
1772
|
env.TELEGRAM_UI_ALLOW_UNSAFE = "false";
|
|
1747
1773
|
}
|
|
1774
|
+
if (
|
|
1775
|
+
!env.TELEGRAM_UI_ALLOW_QUICK_TUNNEL_FALLBACK
|
|
1776
|
+
&& !sourceEnv.TELEGRAM_UI_ALLOW_QUICK_TUNNEL_FALLBACK
|
|
1777
|
+
) {
|
|
1778
|
+
env.TELEGRAM_UI_ALLOW_QUICK_TUNNEL_FALLBACK = "false";
|
|
1779
|
+
}
|
|
1780
|
+
if (
|
|
1781
|
+
!env.TELEGRAM_UI_FALLBACK_AUTH_ENABLED
|
|
1782
|
+
&& !sourceEnv.TELEGRAM_UI_FALLBACK_AUTH_ENABLED
|
|
1783
|
+
) {
|
|
1784
|
+
env.TELEGRAM_UI_FALLBACK_AUTH_ENABLED = "true";
|
|
1785
|
+
}
|
|
1786
|
+
if (
|
|
1787
|
+
!env.CLOUDFLARE_USERNAME_HOSTNAME_POLICY
|
|
1788
|
+
&& !sourceEnv.CLOUDFLARE_USERNAME_HOSTNAME_POLICY
|
|
1789
|
+
) {
|
|
1790
|
+
env.CLOUDFLARE_USERNAME_HOSTNAME_POLICY = "per-user-fixed";
|
|
1791
|
+
}
|
|
1748
1792
|
return true;
|
|
1749
1793
|
}
|
|
1750
1794
|
|
|
@@ -1872,6 +1916,57 @@ function normalizeSetupConfiguration({
|
|
|
1872
1916
|
"internal",
|
|
1873
1917
|
);
|
|
1874
1918
|
|
|
1919
|
+
env.VOICE_ENABLED = toBooleanEnvString(env.VOICE_ENABLED, true);
|
|
1920
|
+
env.VOICE_PROVIDER = normalizeEnum(
|
|
1921
|
+
env.VOICE_PROVIDER,
|
|
1922
|
+
["auto", "openai", "azure", "claude", "gemini", "fallback"],
|
|
1923
|
+
"auto",
|
|
1924
|
+
);
|
|
1925
|
+
env.VOICE_MODEL =
|
|
1926
|
+
env.VOICE_MODEL || "gpt-4o-realtime-preview-2024-12-17";
|
|
1927
|
+
env.VOICE_VISION_MODEL =
|
|
1928
|
+
env.VOICE_VISION_MODEL || "gpt-4.1-mini";
|
|
1929
|
+
env.VOICE_ID = normalizeEnum(
|
|
1930
|
+
env.VOICE_ID,
|
|
1931
|
+
[
|
|
1932
|
+
"alloy",
|
|
1933
|
+
"ash",
|
|
1934
|
+
"ballad",
|
|
1935
|
+
"coral",
|
|
1936
|
+
"echo",
|
|
1937
|
+
"fable",
|
|
1938
|
+
"onyx",
|
|
1939
|
+
"nova",
|
|
1940
|
+
"sage",
|
|
1941
|
+
"shimmer",
|
|
1942
|
+
"verse",
|
|
1943
|
+
],
|
|
1944
|
+
"alloy",
|
|
1945
|
+
);
|
|
1946
|
+
env.VOICE_TURN_DETECTION = normalizeEnum(
|
|
1947
|
+
env.VOICE_TURN_DETECTION,
|
|
1948
|
+
["server_vad", "semantic_vad", "none"],
|
|
1949
|
+
"server_vad",
|
|
1950
|
+
);
|
|
1951
|
+
env.VOICE_FALLBACK_MODE = normalizeEnum(
|
|
1952
|
+
env.VOICE_FALLBACK_MODE,
|
|
1953
|
+
["browser", "disabled"],
|
|
1954
|
+
"browser",
|
|
1955
|
+
);
|
|
1956
|
+
env.VOICE_DELEGATE_EXECUTOR = normalizeEnum(
|
|
1957
|
+
env.VOICE_DELEGATE_EXECUTOR,
|
|
1958
|
+
[
|
|
1959
|
+
"codex-sdk",
|
|
1960
|
+
"copilot-sdk",
|
|
1961
|
+
"claude-sdk",
|
|
1962
|
+
"gemini-sdk",
|
|
1963
|
+
"opencode-sdk",
|
|
1964
|
+
],
|
|
1965
|
+
env.PRIMARY_AGENT || "codex-sdk",
|
|
1966
|
+
);
|
|
1967
|
+
env.AZURE_OPENAI_REALTIME_DEPLOYMENT =
|
|
1968
|
+
env.AZURE_OPENAI_REALTIME_DEPLOYMENT || "gpt-4o-realtime-preview";
|
|
1969
|
+
|
|
1875
1970
|
env.CODEX_MODEL_PROFILE = normalizeEnum(
|
|
1876
1971
|
env.CODEX_MODEL_PROFILE,
|
|
1877
1972
|
["xl", "m"],
|
|
@@ -1940,6 +2035,11 @@ function normalizeSetupConfiguration({
|
|
|
1940
2035
|
["sdk", "auto", "cli"],
|
|
1941
2036
|
"sdk",
|
|
1942
2037
|
);
|
|
2038
|
+
env.GEMINI_TRANSPORT = normalizeEnum(
|
|
2039
|
+
env.GEMINI_TRANSPORT || process.env.GEMINI_TRANSPORT,
|
|
2040
|
+
["sdk", "auto", "cli"],
|
|
2041
|
+
"auto",
|
|
2042
|
+
);
|
|
1943
2043
|
|
|
1944
2044
|
env.WHATSAPP_ENABLED = toBooleanEnvString(env.WHATSAPP_ENABLED, false);
|
|
1945
2045
|
|
|
@@ -1983,7 +2083,13 @@ function normalizeSetupConfiguration({
|
|
|
1983
2083
|
{
|
|
1984
2084
|
const primaryExec = configJson.executors.find((e) => e.role === "primary");
|
|
1985
2085
|
if (primaryExec) {
|
|
1986
|
-
const sdkMap = {
|
|
2086
|
+
const sdkMap = {
|
|
2087
|
+
CODEX: "codex-sdk",
|
|
2088
|
+
COPILOT: "copilot-sdk",
|
|
2089
|
+
CLAUDE: "claude-sdk",
|
|
2090
|
+
GEMINI: "gemini-sdk",
|
|
2091
|
+
OPENCODE: "opencode-sdk",
|
|
2092
|
+
};
|
|
1987
2093
|
env.PRIMARY_AGENT = env.PRIMARY_AGENT ||
|
|
1988
2094
|
sdkMap[String(primaryExec.executor).toUpperCase()] || "codex-sdk";
|
|
1989
2095
|
}
|
|
@@ -2709,6 +2815,8 @@ async function main() {
|
|
|
2709
2815
|
"Copilot + Codex (50/50 split)",
|
|
2710
2816
|
"Copilot only (Claude Opus 4.6)",
|
|
2711
2817
|
"Claude only (direct API)",
|
|
2818
|
+
"Gemini only (direct SDK + CLI fallback)",
|
|
2819
|
+
"Gemini + Codex (60/40 split)",
|
|
2712
2820
|
"OpenCode only (local OpenCode server)",
|
|
2713
2821
|
"OpenCode + Codex (60/40 split)",
|
|
2714
2822
|
"Triple (Copilot Claude 40%, Codex 35%, Copilot GPT 25%)",
|
|
@@ -2719,6 +2827,8 @@ async function main() {
|
|
|
2719
2827
|
"Copilot + Codex (50/50 split)",
|
|
2720
2828
|
"Copilot only (Claude Opus 4.6)",
|
|
2721
2829
|
"Claude only (direct API)",
|
|
2830
|
+
"Gemini only (direct SDK + CLI fallback)",
|
|
2831
|
+
"Gemini + Codex (60/40 split)",
|
|
2722
2832
|
"OpenCode only (local OpenCode server)",
|
|
2723
2833
|
"OpenCode + Codex (60/40 split)",
|
|
2724
2834
|
"Triple (Copilot Claude 40%, Codex 35%, Copilot GPT 25%)",
|
|
@@ -2731,8 +2841,29 @@ async function main() {
|
|
|
2731
2841
|
);
|
|
2732
2842
|
|
|
2733
2843
|
const presetNames = isAdvancedSetup
|
|
2734
|
-
? [
|
|
2735
|
-
|
|
2844
|
+
? [
|
|
2845
|
+
"codex-only",
|
|
2846
|
+
"copilot-codex",
|
|
2847
|
+
"copilot-only",
|
|
2848
|
+
"claude-only",
|
|
2849
|
+
"gemini-only",
|
|
2850
|
+
"gemini-codex",
|
|
2851
|
+
"opencode-only",
|
|
2852
|
+
"opencode-codex",
|
|
2853
|
+
"triple",
|
|
2854
|
+
"custom",
|
|
2855
|
+
]
|
|
2856
|
+
: [
|
|
2857
|
+
"codex-only",
|
|
2858
|
+
"copilot-codex",
|
|
2859
|
+
"copilot-only",
|
|
2860
|
+
"claude-only",
|
|
2861
|
+
"gemini-only",
|
|
2862
|
+
"gemini-codex",
|
|
2863
|
+
"opencode-only",
|
|
2864
|
+
"opencode-codex",
|
|
2865
|
+
"triple",
|
|
2866
|
+
];
|
|
2736
2867
|
const presetKey = presetNames[presetIdx] || "codex-only";
|
|
2737
2868
|
|
|
2738
2869
|
if (presetKey === "custom") {
|
|
@@ -2857,6 +2988,12 @@ async function main() {
|
|
|
2857
2988
|
if (!usedSdks.has("CODEX")) { env.CODEX_SDK_DISABLED = "true"; } else { delete env.CODEX_SDK_DISABLED; }
|
|
2858
2989
|
if (!usedSdks.has("COPILOT")) { env.COPILOT_SDK_DISABLED = "true"; } else { delete env.COPILOT_SDK_DISABLED; }
|
|
2859
2990
|
if (!usedSdks.has("CLAUDE")) { env.CLAUDE_SDK_DISABLED = "true"; } else { delete env.CLAUDE_SDK_DISABLED; }
|
|
2991
|
+
if (!usedSdks.has("GEMINI")) { env.GEMINI_SDK_DISABLED = "true"; } else { delete env.GEMINI_SDK_DISABLED; }
|
|
2992
|
+
if (!usedSdks.has("OPENCODE")) {
|
|
2993
|
+
env.OPENCODE_SDK_DISABLED = "true";
|
|
2994
|
+
} else {
|
|
2995
|
+
delete env.OPENCODE_SDK_DISABLED;
|
|
2996
|
+
}
|
|
2860
2997
|
|
|
2861
2998
|
if (isAdvancedSetup) {
|
|
2862
2999
|
console.log();
|
|
@@ -2884,6 +3021,20 @@ async function main() {
|
|
|
2884
3021
|
);
|
|
2885
3022
|
if (!wantClaudeFallback) env.CLAUDE_SDK_DISABLED = "true";
|
|
2886
3023
|
else delete env.CLAUDE_SDK_DISABLED;
|
|
3024
|
+
|
|
3025
|
+
const wantGeminiFallback = usedSdks.has("GEMINI") || await prompt.confirm(
|
|
3026
|
+
"Enable Gemini SDK fallback? (requires GEMINI_API_KEY or GOOGLE_API_KEY)",
|
|
3027
|
+
!!process.env.GEMINI_API_KEY || !!process.env.GOOGLE_API_KEY,
|
|
3028
|
+
);
|
|
3029
|
+
if (!wantGeminiFallback) env.GEMINI_SDK_DISABLED = "true";
|
|
3030
|
+
else delete env.GEMINI_SDK_DISABLED;
|
|
3031
|
+
|
|
3032
|
+
const wantOpenCodeFallback = usedSdks.has("OPENCODE") || await prompt.confirm(
|
|
3033
|
+
"Enable OpenCode SDK fallback? (requires opencode binary on PATH)",
|
|
3034
|
+
false,
|
|
3035
|
+
);
|
|
3036
|
+
if (!wantOpenCodeFallback) env.OPENCODE_SDK_DISABLED = "true";
|
|
3037
|
+
else delete env.OPENCODE_SDK_DISABLED;
|
|
2887
3038
|
}
|
|
2888
3039
|
saveSetupSnapshot(4, "Executor / Agent Configuration", env, configJson);
|
|
2889
3040
|
} // end step 4
|
|
@@ -2906,6 +3057,8 @@ async function main() {
|
|
|
2906
3057
|
const needsCodexSdk = usedSdks.has("CODEX") || env.CODEX_SDK_DISABLED !== "true";
|
|
2907
3058
|
const needsCopilotSdk = usedSdks.has("COPILOT") || env.COPILOT_SDK_DISABLED !== "true";
|
|
2908
3059
|
const needsClaudeSdk = usedSdks.has("CLAUDE") || env.CLAUDE_SDK_DISABLED !== "true";
|
|
3060
|
+
const needsGeminiSdk = usedSdks.has("GEMINI") || env.GEMINI_SDK_DISABLED !== "true";
|
|
3061
|
+
const needsOpencodeSdk = usedSdks.has("OPENCODE") || env.OPENCODE_SDK_DISABLED !== "true";
|
|
2909
3062
|
|
|
2910
3063
|
// ── 5a. Copilot / GitHub Token ──────────────────────
|
|
2911
3064
|
if (needsCopilotSdk) {
|
|
@@ -3031,6 +3184,168 @@ async function main() {
|
|
|
3031
3184
|
// Codex not needed — skip OpenAI key prompts entirely
|
|
3032
3185
|
info("Codex SDK not in executor preset — skipping OpenAI configuration.");
|
|
3033
3186
|
}
|
|
3187
|
+
|
|
3188
|
+
// ── 5d. Gemini / Google AI Key ────────────────────────
|
|
3189
|
+
if (needsGeminiSdk) {
|
|
3190
|
+
console.log(chalk.bold("\n Gemini SDK") + chalk.dim(" (uses Google AI API key)\n"));
|
|
3191
|
+
const existingGeminiKey =
|
|
3192
|
+
process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "";
|
|
3193
|
+
if (existingGeminiKey) {
|
|
3194
|
+
info(`Gemini API key detected (${existingGeminiKey.slice(0, 8)}…). Gemini SDK will use it.`);
|
|
3195
|
+
} else {
|
|
3196
|
+
const geminiKey = await prompt.ask(
|
|
3197
|
+
"Gemini API Key (GEMINI_API_KEY / GOOGLE_API_KEY, blank to skip)",
|
|
3198
|
+
"",
|
|
3199
|
+
);
|
|
3200
|
+
if (geminiKey) {
|
|
3201
|
+
env.GEMINI_API_KEY = geminiKey;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
env.GEMINI_MODEL =
|
|
3205
|
+
env.GEMINI_MODEL ||
|
|
3206
|
+
process.env.GEMINI_MODEL ||
|
|
3207
|
+
"gemini-2.5-pro";
|
|
3208
|
+
const geminiTransportIdx = await prompt.choose(
|
|
3209
|
+
"Gemini transport mode:",
|
|
3210
|
+
[
|
|
3211
|
+
"auto (recommended: SDK first, CLI fallback)",
|
|
3212
|
+
"sdk only",
|
|
3213
|
+
"cli only",
|
|
3214
|
+
],
|
|
3215
|
+
0,
|
|
3216
|
+
);
|
|
3217
|
+
env.GEMINI_TRANSPORT =
|
|
3218
|
+
geminiTransportIdx === 1
|
|
3219
|
+
? "sdk"
|
|
3220
|
+
: geminiTransportIdx === 2
|
|
3221
|
+
? "cli"
|
|
3222
|
+
: "auto";
|
|
3223
|
+
if (env.GEMINI_TRANSPORT !== "sdk") {
|
|
3224
|
+
const cliPath = await prompt.ask(
|
|
3225
|
+
"Gemini CLI binary path (GEMINI_CLI_PATH, leave blank for default 'gemini')",
|
|
3226
|
+
process.env.GEMINI_CLI_PATH || "",
|
|
3227
|
+
);
|
|
3228
|
+
if (cliPath) {
|
|
3229
|
+
env.GEMINI_CLI_PATH = cliPath;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
} else {
|
|
3233
|
+
info("Gemini SDK not in executor preset — skipping Gemini configuration.");
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
// ── 5e. OpenCode local server ─────────────────────────
|
|
3237
|
+
if (needsOpencodeSdk) {
|
|
3238
|
+
console.log(chalk.bold("\n OpenCode SDK") + chalk.dim(" (uses local opencode server)\n"));
|
|
3239
|
+
env.OPENCODE_PORT = String(
|
|
3240
|
+
toPositiveInt(
|
|
3241
|
+
env.OPENCODE_PORT || process.env.OPENCODE_PORT || "4096",
|
|
3242
|
+
4096,
|
|
3243
|
+
),
|
|
3244
|
+
);
|
|
3245
|
+
env.OPENCODE_MODEL =
|
|
3246
|
+
env.OPENCODE_MODEL ||
|
|
3247
|
+
process.env.OPENCODE_MODEL ||
|
|
3248
|
+
"gpt-5.2-codex";
|
|
3249
|
+
env.OPENCODE_TIMEOUT_MS = String(
|
|
3250
|
+
toPositiveInt(
|
|
3251
|
+
env.OPENCODE_TIMEOUT_MS || process.env.OPENCODE_TIMEOUT_MS || "3600000",
|
|
3252
|
+
3600000,
|
|
3253
|
+
),
|
|
3254
|
+
);
|
|
3255
|
+
info(
|
|
3256
|
+
`OpenCode defaults: OPENCODE_PORT=${env.OPENCODE_PORT}, OPENCODE_MODEL=${env.OPENCODE_MODEL}, OPENCODE_TIMEOUT_MS=${env.OPENCODE_TIMEOUT_MS}`,
|
|
3257
|
+
);
|
|
3258
|
+
} else {
|
|
3259
|
+
info("OpenCode SDK not in executor preset — skipping OpenCode configuration.");
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
// ── 5f. Voice Mode ─────────────────────────────────────
|
|
3263
|
+
console.log(chalk.bold("\n Voice Mode") + chalk.dim(" (Realtime voice assistant)\n"));
|
|
3264
|
+
const enableVoice = await prompt.confirm(
|
|
3265
|
+
"Enable voice mode in the UI?",
|
|
3266
|
+
parseBooleanEnvValue(process.env.VOICE_ENABLED, true),
|
|
3267
|
+
);
|
|
3268
|
+
env.VOICE_ENABLED = enableVoice ? "true" : "false";
|
|
3269
|
+
if (enableVoice) {
|
|
3270
|
+
const providerIdx = await prompt.choose(
|
|
3271
|
+
"Voice provider:",
|
|
3272
|
+
[
|
|
3273
|
+
"auto (recommended)",
|
|
3274
|
+
"openai",
|
|
3275
|
+
"azure",
|
|
3276
|
+
"claude (fallback voice + Claude vision)",
|
|
3277
|
+
"gemini (fallback voice + Gemini vision)",
|
|
3278
|
+
"fallback (browser speech only)",
|
|
3279
|
+
],
|
|
3280
|
+
0,
|
|
3281
|
+
);
|
|
3282
|
+
const providerOptions = ["auto", "openai", "azure", "claude", "gemini", "fallback"];
|
|
3283
|
+
env.VOICE_PROVIDER = providerOptions[providerIdx] || "auto";
|
|
3284
|
+
|
|
3285
|
+
env.VOICE_ID = await prompt.ask(
|
|
3286
|
+
"Voice ID",
|
|
3287
|
+
process.env.VOICE_ID || "alloy",
|
|
3288
|
+
);
|
|
3289
|
+
env.VOICE_MODEL = await prompt.ask(
|
|
3290
|
+
"Realtime voice model",
|
|
3291
|
+
process.env.VOICE_MODEL || "gpt-4o-realtime-preview-2024-12-17",
|
|
3292
|
+
);
|
|
3293
|
+
env.VOICE_VISION_MODEL = await prompt.ask(
|
|
3294
|
+
"Vision model for camera/screen analysis",
|
|
3295
|
+
process.env.VOICE_VISION_MODEL || "gpt-4.1-mini",
|
|
3296
|
+
);
|
|
3297
|
+
env.VOICE_TURN_DETECTION = await prompt.ask(
|
|
3298
|
+
"Turn detection (server_vad|semantic_vad|none)",
|
|
3299
|
+
process.env.VOICE_TURN_DETECTION || "server_vad",
|
|
3300
|
+
);
|
|
3301
|
+
env.VOICE_FALLBACK_MODE = await prompt.ask(
|
|
3302
|
+
"Fallback mode (browser|disabled)",
|
|
3303
|
+
process.env.VOICE_FALLBACK_MODE || "browser",
|
|
3304
|
+
);
|
|
3305
|
+
env.VOICE_DELEGATE_EXECUTOR = await prompt.ask(
|
|
3306
|
+
"Delegate executor (codex-sdk|copilot-sdk|claude-sdk|gemini-sdk|opencode-sdk)",
|
|
3307
|
+
process.env.VOICE_DELEGATE_EXECUTOR || env.PRIMARY_AGENT || "codex-sdk",
|
|
3308
|
+
);
|
|
3309
|
+
if (env.VOICE_PROVIDER === "openai" || env.VOICE_PROVIDER === "auto") {
|
|
3310
|
+
env.OPENAI_REALTIME_API_KEY = await prompt.ask(
|
|
3311
|
+
"OpenAI Realtime key (OPENAI_REALTIME_API_KEY, blank uses OPENAI_API_KEY)",
|
|
3312
|
+
process.env.OPENAI_REALTIME_API_KEY || env.OPENAI_API_KEY || "",
|
|
3313
|
+
);
|
|
3314
|
+
}
|
|
3315
|
+
if (env.VOICE_PROVIDER === "azure" || env.VOICE_PROVIDER === "auto") {
|
|
3316
|
+
env.AZURE_OPENAI_REALTIME_ENDPOINT = await prompt.ask(
|
|
3317
|
+
"Azure Realtime endpoint (AZURE_OPENAI_REALTIME_ENDPOINT)",
|
|
3318
|
+
process.env.AZURE_OPENAI_REALTIME_ENDPOINT || process.env.AZURE_OPENAI_ENDPOINT || "",
|
|
3319
|
+
);
|
|
3320
|
+
env.AZURE_OPENAI_REALTIME_API_KEY = await prompt.ask(
|
|
3321
|
+
"Azure Realtime key (AZURE_OPENAI_REALTIME_API_KEY, blank uses AZURE_OPENAI_API_KEY)",
|
|
3322
|
+
process.env.AZURE_OPENAI_REALTIME_API_KEY || process.env.AZURE_OPENAI_API_KEY || "",
|
|
3323
|
+
);
|
|
3324
|
+
env.AZURE_OPENAI_REALTIME_DEPLOYMENT = await prompt.ask(
|
|
3325
|
+
"Azure Realtime deployment (AZURE_OPENAI_REALTIME_DEPLOYMENT)",
|
|
3326
|
+
process.env.AZURE_OPENAI_REALTIME_DEPLOYMENT || "gpt-4o-realtime-preview",
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
if (env.VOICE_PROVIDER === "claude" && !env.ANTHROPIC_API_KEY) {
|
|
3330
|
+
env.ANTHROPIC_API_KEY = await prompt.ask(
|
|
3331
|
+
"Anthropic API key for Claude voice/vision fallback (ANTHROPIC_API_KEY)",
|
|
3332
|
+
process.env.ANTHROPIC_API_KEY || "",
|
|
3333
|
+
);
|
|
3334
|
+
}
|
|
3335
|
+
if (
|
|
3336
|
+
env.VOICE_PROVIDER === "gemini" &&
|
|
3337
|
+
!env.GEMINI_API_KEY &&
|
|
3338
|
+
!env.GOOGLE_API_KEY
|
|
3339
|
+
) {
|
|
3340
|
+
const geminiVoiceKey = await prompt.ask(
|
|
3341
|
+
"Gemini API key for Gemini voice/vision fallback (GEMINI_API_KEY or GOOGLE_API_KEY)",
|
|
3342
|
+
process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "",
|
|
3343
|
+
);
|
|
3344
|
+
if (geminiVoiceKey) {
|
|
3345
|
+
env.GEMINI_API_KEY = geminiVoiceKey;
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3034
3349
|
saveSetupSnapshot(5, "AI Provider Keys", env, configJson);
|
|
3035
3350
|
} // end step 5
|
|
3036
3351
|
|
|
@@ -3226,7 +3541,7 @@ async function main() {
|
|
|
3226
3541
|
env.PROJECT_NAME || configJson.projectName || "Unknown",
|
|
3227
3542
|
);
|
|
3228
3543
|
const testMsg =
|
|
3229
|
-
"
|
|
3544
|
+
":bot: <b>Telegram Bot Test</b>\n\n" +
|
|
3230
3545
|
"Your bosun Telegram bot is configured correctly!\n\n" +
|
|
3231
3546
|
`Project: ${projectLabel}\n` +
|
|
3232
3547
|
"Try: /status, /tasks, /help";
|
|
@@ -5106,6 +5421,25 @@ async function runNonInteractive({
|
|
|
5106
5421
|
env.GITHUB_REPO = env.GITHUB_REPOSITORY;
|
|
5107
5422
|
}
|
|
5108
5423
|
env.OPENAI_API_KEY = process.env.OPENAI_API_KEY || "";
|
|
5424
|
+
env.VOICE_ENABLED = process.env.VOICE_ENABLED || "true";
|
|
5425
|
+
env.VOICE_PROVIDER = process.env.VOICE_PROVIDER || "auto";
|
|
5426
|
+
env.VOICE_MODEL =
|
|
5427
|
+
process.env.VOICE_MODEL || "gpt-4o-realtime-preview-2024-12-17";
|
|
5428
|
+
env.VOICE_VISION_MODEL = process.env.VOICE_VISION_MODEL || "gpt-4.1-mini";
|
|
5429
|
+
env.OPENAI_REALTIME_API_KEY = process.env.OPENAI_REALTIME_API_KEY || "";
|
|
5430
|
+
env.AZURE_OPENAI_REALTIME_ENDPOINT =
|
|
5431
|
+
process.env.AZURE_OPENAI_REALTIME_ENDPOINT || "";
|
|
5432
|
+
env.AZURE_OPENAI_REALTIME_API_KEY =
|
|
5433
|
+
process.env.AZURE_OPENAI_REALTIME_API_KEY || "";
|
|
5434
|
+
env.AZURE_OPENAI_REALTIME_DEPLOYMENT =
|
|
5435
|
+
process.env.AZURE_OPENAI_REALTIME_DEPLOYMENT || "gpt-4o-realtime-preview";
|
|
5436
|
+
env.VOICE_ID = process.env.VOICE_ID || "alloy";
|
|
5437
|
+
env.VOICE_TURN_DETECTION = process.env.VOICE_TURN_DETECTION || "server_vad";
|
|
5438
|
+
env.VOICE_FALLBACK_MODE = process.env.VOICE_FALLBACK_MODE || "browser";
|
|
5439
|
+
env.VOICE_DELEGATE_EXECUTOR =
|
|
5440
|
+
process.env.VOICE_DELEGATE_EXECUTOR ||
|
|
5441
|
+
process.env.PRIMARY_AGENT ||
|
|
5442
|
+
"codex-sdk";
|
|
5109
5443
|
env.CODEX_MODEL_PROFILE = process.env.CODEX_MODEL_PROFILE || "xl";
|
|
5110
5444
|
env.CODEX_MODEL_PROFILE_SUBAGENT =
|
|
5111
5445
|
process.env.CODEX_MODEL_PROFILE_SUBAGENT ||
|
|
@@ -5191,6 +5525,8 @@ async function runNonInteractive({
|
|
|
5191
5525
|
// Smart default: pick preset based on available API keys
|
|
5192
5526
|
const hasOpenAI = !!process.env.OPENAI_API_KEY;
|
|
5193
5527
|
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
|
5528
|
+
const hasGemini =
|
|
5529
|
+
!!process.env.GEMINI_API_KEY || !!process.env.GOOGLE_API_KEY;
|
|
5194
5530
|
const hasGitHub = !!process.env.GITHUB_TOKEN || !!process.env.COPILOT_CLI_TOKEN;
|
|
5195
5531
|
const presetEnv = (process.env.EXECUTOR_PRESET || "").toLowerCase();
|
|
5196
5532
|
if (presetEnv && EXECUTOR_PRESETS[presetEnv]) {
|
|
@@ -5201,6 +5537,10 @@ async function runNonInteractive({
|
|
|
5201
5537
|
: EXECUTOR_PRESETS["copilot-only"];
|
|
5202
5538
|
} else if (hasAnthropic) {
|
|
5203
5539
|
configJson.executors = EXECUTOR_PRESETS["claude-only"];
|
|
5540
|
+
} else if (hasGemini) {
|
|
5541
|
+
configJson.executors = hasOpenAI
|
|
5542
|
+
? EXECUTOR_PRESETS["gemini-codex"]
|
|
5543
|
+
: EXECUTOR_PRESETS["gemini-only"];
|
|
5204
5544
|
} else {
|
|
5205
5545
|
configJson.executors = EXECUTOR_PRESETS["codex-only"];
|
|
5206
5546
|
}
|
|
@@ -5212,7 +5552,13 @@ async function runNonInteractive({
|
|
|
5212
5552
|
(e) => e.role === "primary",
|
|
5213
5553
|
);
|
|
5214
5554
|
if (primaryExec) {
|
|
5215
|
-
const sdkMap = {
|
|
5555
|
+
const sdkMap = {
|
|
5556
|
+
CODEX: "codex-sdk",
|
|
5557
|
+
COPILOT: "copilot-sdk",
|
|
5558
|
+
CLAUDE: "claude-sdk",
|
|
5559
|
+
GEMINI: "gemini-sdk",
|
|
5560
|
+
OPENCODE: "opencode-sdk",
|
|
5561
|
+
};
|
|
5216
5562
|
env.PRIMARY_AGENT = sdkMap[String(primaryExec.executor).toUpperCase()] || "codex-sdk";
|
|
5217
5563
|
}
|
|
5218
5564
|
}
|
|
@@ -5321,7 +5667,7 @@ async function runNonInteractive({
|
|
|
5321
5667
|
if (r.success) {
|
|
5322
5668
|
info(` ✓ Workspace repo ready: ${r.name}`);
|
|
5323
5669
|
} else {
|
|
5324
|
-
warn(`
|
|
5670
|
+
warn(` :alert: Workspace repo ${r.name}: ${r.error}`);
|
|
5325
5671
|
}
|
|
5326
5672
|
}
|
|
5327
5673
|
// Set BOSUN_AGENT_REPO_ROOT to workspace primary repo
|
|
@@ -5580,7 +5926,7 @@ async function writeConfigFiles({ env, configJson, repoRoot, configDir }) {
|
|
|
5580
5926
|
" ╔═══════════════════════════════════════════════════════════════╗",
|
|
5581
5927
|
);
|
|
5582
5928
|
console.log(
|
|
5583
|
-
" ║
|
|
5929
|
+
" ║ :check: Setup Complete! ║",
|
|
5584
5930
|
);
|
|
5585
5931
|
console.log(
|
|
5586
5932
|
" ╚═══════════════════════════════════════════════════════════════╝",
|
|
@@ -5621,6 +5967,15 @@ async function writeConfigFiles({ env, configJson, repoRoot, configDir }) {
|
|
|
5621
5967
|
) {
|
|
5622
5968
|
info("No API key set — AI analysis & autofix will be disabled.");
|
|
5623
5969
|
}
|
|
5970
|
+
if (
|
|
5971
|
+
!env.GEMINI_API_KEY &&
|
|
5972
|
+
!env.GOOGLE_API_KEY &&
|
|
5973
|
+
!process.env.GEMINI_API_KEY &&
|
|
5974
|
+
!process.env.GOOGLE_API_KEY &&
|
|
5975
|
+
!parseBooleanEnvValue(env.GEMINI_SDK_DISABLED, false)
|
|
5976
|
+
) {
|
|
5977
|
+
info("No Gemini key set — Gemini SDK will be unavailable until configured.");
|
|
5978
|
+
}
|
|
5624
5979
|
|
|
5625
5980
|
console.log("");
|
|
5626
5981
|
console.log(chalk.bold(" Next steps:"));
|
package/shared-knowledge.mjs
CHANGED
|
@@ -400,7 +400,7 @@ export function getKnowledgeState() {
|
|
|
400
400
|
|
|
401
401
|
export function formatKnowledgeSummary() {
|
|
402
402
|
return [
|
|
403
|
-
|
|
403
|
+
`:u1f4da: Shared Knowledge: ${knowledgeState.entriesWritten} entries written this session`,
|
|
404
404
|
`Target: ${knowledgeState.targetFile}`,
|
|
405
405
|
`Dedup cache: ${knowledgeState.entryHashes.size} hashes`,
|
|
406
406
|
knowledgeState.lastWriteAt
|
package/startup-service.mjs
CHANGED
|
@@ -344,7 +344,7 @@ async function installWindows(options = {}) {
|
|
|
344
344
|
|
|
345
345
|
// Strategy 2: Elevate via UAC prompt
|
|
346
346
|
console.log(
|
|
347
|
-
"
|
|
347
|
+
" :help: Admin access required — requesting elevation (UAC prompt)...",
|
|
348
348
|
);
|
|
349
349
|
|
|
350
350
|
// Delete + Create via elevated process
|
|
@@ -370,7 +370,7 @@ async function installWindows(options = {}) {
|
|
|
370
370
|
|
|
371
371
|
// Strategy 3: Fall back to Startup folder (no admin needed)
|
|
372
372
|
console.log(
|
|
373
|
-
"
|
|
373
|
+
" :alert: Task Scheduler elevation failed — falling back to Startup folder.",
|
|
374
374
|
);
|
|
375
375
|
console.log(
|
|
376
376
|
" (Startup folder works without admin, but has no auto-restart on failure)",
|
|
@@ -394,7 +394,7 @@ async function removeWindows() {
|
|
|
394
394
|
|
|
395
395
|
if (isAccessDenied) {
|
|
396
396
|
console.log(
|
|
397
|
-
"
|
|
397
|
+
" :help: Admin access required — requesting elevation (UAC prompt)...",
|
|
398
398
|
);
|
|
399
399
|
const elevated = runElevated(`/Delete /TN "${TASK_NAME}" /F`);
|
|
400
400
|
results.push({
|
|
@@ -543,7 +543,7 @@ async function installMacOS(options = {}) {
|
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
// Try with sudo — prompts for password in terminal via osascript or direct sudo
|
|
546
|
-
console.log("
|
|
546
|
+
console.log(" :help: Permission required — requesting sudo access...");
|
|
547
547
|
try {
|
|
548
548
|
// Write plist to temp location first
|
|
549
549
|
const tmpPlist = resolve(__dirname, ".cache", `${SERVICE_LABEL}.plist`);
|
|
@@ -614,7 +614,7 @@ async function removeMacOS() {
|
|
|
614
614
|
}
|
|
615
615
|
|
|
616
616
|
// Elevate via osascript
|
|
617
|
-
console.log("
|
|
617
|
+
console.log(" :help: Permission required — requesting sudo access...");
|
|
618
618
|
try {
|
|
619
619
|
const escapedPlistPath = plistPath.replace(/'/g, "'\\''");
|
|
620
620
|
const script = `do shell script "launchctl unload '${escapedPlistPath}' 2>/dev/null; rm -f '${escapedPlistPath}'" with administrator privileges`;
|
|
@@ -752,7 +752,7 @@ async function installLinux(options = {}) {
|
|
|
752
752
|
}
|
|
753
753
|
|
|
754
754
|
// Try with sudo for the systemctl commands (unit file is user-space)
|
|
755
|
-
console.log("
|
|
755
|
+
console.log(" :help: Permission required — trying sudo...");
|
|
756
756
|
try {
|
|
757
757
|
// The unit file write doesn't need sudo (it's in ~/.config)
|
|
758
758
|
// but systemctl might if the session isn't fully initialized
|
|
@@ -772,13 +772,13 @@ async function installLinux(options = {}) {
|
|
|
772
772
|
};
|
|
773
773
|
} catch (sudoErr) {
|
|
774
774
|
console.log(
|
|
775
|
-
"
|
|
775
|
+
" :alert: systemd with sudo failed — falling back to crontab.",
|
|
776
776
|
);
|
|
777
777
|
// Fall through to crontab
|
|
778
778
|
}
|
|
779
779
|
}
|
|
780
780
|
} else {
|
|
781
|
-
console.log("
|
|
781
|
+
console.log(" :help: systemd user session not available — using crontab.");
|
|
782
782
|
}
|
|
783
783
|
|
|
784
784
|
// Strategy 2: crontab @reboot fallback (works everywhere, no root needed)
|
|
@@ -865,7 +865,7 @@ async function removeLinux() {
|
|
|
865
865
|
err.message?.includes("EACCES");
|
|
866
866
|
|
|
867
867
|
if (isPermission) {
|
|
868
|
-
console.log("
|
|
868
|
+
console.log(" :help: Permission required — trying sudo...");
|
|
869
869
|
try {
|
|
870
870
|
execSync(`sudo systemctl --user stop ${SYSTEMD_UNIT}`, {
|
|
871
871
|
stdio: "inherit",
|