bosun 0.36.2 → 0.36.4

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.
Files changed (57) hide show
  1. package/agent-prompts.mjs +95 -0
  2. package/analyze-agent-work-helpers.mjs +308 -0
  3. package/analyze-agent-work.mjs +926 -0
  4. package/autofix.mjs +2 -0
  5. package/bosun.schema.json +101 -3
  6. package/codex-shell.mjs +85 -10
  7. package/desktop/main.mjs +871 -48
  8. package/desktop/preload.mjs +54 -1
  9. package/desktop-shortcut.mjs +90 -11
  10. package/git-editor-fix.mjs +273 -0
  11. package/mcp-registry.mjs +579 -0
  12. package/meeting-workflow-service.mjs +631 -0
  13. package/monitor.mjs +18 -103
  14. package/package.json +21 -2
  15. package/primary-agent.mjs +32 -12
  16. package/session-tracker.mjs +68 -0
  17. package/setup-web-server.mjs +20 -10
  18. package/setup.mjs +376 -83
  19. package/startup-service.mjs +51 -6
  20. package/stream-resilience.mjs +17 -7
  21. package/ui/app.js +164 -4
  22. package/ui/components/agent-selector.js +145 -1
  23. package/ui/components/chat-view.js +161 -15
  24. package/ui/components/session-list.js +2 -2
  25. package/ui/components/shared.js +188 -15
  26. package/ui/modules/icons.js +13 -0
  27. package/ui/modules/utils.js +44 -0
  28. package/ui/modules/voice-client-sdk.js +733 -0
  29. package/ui/modules/voice-overlay.js +128 -15
  30. package/ui/modules/voice.js +15 -6
  31. package/ui/setup.html +281 -81
  32. package/ui/styles/components.css +99 -3
  33. package/ui/styles/sessions.css +122 -14
  34. package/ui/styles.css +14 -0
  35. package/ui/tabs/agents.js +1 -1
  36. package/ui/tabs/chat.js +123 -14
  37. package/ui/tabs/control.js +16 -22
  38. package/ui/tabs/dashboard.js +85 -8
  39. package/ui/tabs/library.js +113 -17
  40. package/ui/tabs/settings.js +116 -2
  41. package/ui/tabs/tasks.js +388 -39
  42. package/ui/tabs/telemetry.js +0 -1
  43. package/ui/tabs/workflows.js +4 -0
  44. package/ui-server.mjs +400 -22
  45. package/update-check.mjs +41 -13
  46. package/voice-action-dispatcher.mjs +844 -0
  47. package/voice-agents-sdk.mjs +664 -0
  48. package/voice-auth-manager.mjs +164 -0
  49. package/voice-relay.mjs +1194 -0
  50. package/voice-tools.mjs +914 -0
  51. package/workflow-templates/agents.mjs +6 -2
  52. package/workflow-templates/github.mjs +154 -12
  53. package/workflow-templates.mjs +3 -0
  54. package/github-reconciler.mjs +0 -506
  55. package/merge-strategy.mjs +0 -1210
  56. package/pr-cleanup-daemon.mjs +0 -992
  57. package/workspace-reaper.mjs +0 -405
@@ -0,0 +1,164 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+
5
+ const VOICE_AUTH_STATE_PATH = join(homedir(), ".bosun", "voice-auth-state.json");
6
+ const STATE_TTL_MS = 15_000;
7
+
8
+ let _cachedState = null;
9
+ let _cachedStateAt = 0;
10
+
11
+ function normalizeProvider(provider) {
12
+ return String(provider || "").trim().toLowerCase();
13
+ }
14
+
15
+ function readStateFile() {
16
+ if (!existsSync(VOICE_AUTH_STATE_PATH)) return {};
17
+ const raw = readFileSync(VOICE_AUTH_STATE_PATH, "utf8");
18
+ const parsed = JSON.parse(raw);
19
+ if (!parsed || typeof parsed !== "object") return {};
20
+ return parsed;
21
+ }
22
+
23
+ function getCachedState(forceReload = false) {
24
+ const isFresh = !forceReload && _cachedState && Date.now() - _cachedStateAt < STATE_TTL_MS;
25
+ if (isFresh) return _cachedState;
26
+
27
+ try {
28
+ _cachedState = readStateFile();
29
+ } catch {
30
+ _cachedState = {};
31
+ }
32
+ _cachedStateAt = Date.now();
33
+ return _cachedState;
34
+ }
35
+
36
+ function isExpired(expiresAt) {
37
+ if (!expiresAt) return false;
38
+ const ts = Number(new Date(expiresAt).getTime());
39
+ if (Number.isNaN(ts)) return false;
40
+ return ts <= Date.now() + 30_000;
41
+ }
42
+
43
+ function getProviderEnvCandidates(provider) {
44
+ switch (provider) {
45
+ case "openai":
46
+ return [
47
+ process.env.BOSUN_VOICE_OPENAI_ACCESS_TOKEN,
48
+ process.env.OPENAI_OAUTH_ACCESS_TOKEN,
49
+ process.env.OPENAI_ACCESS_TOKEN,
50
+ ];
51
+ case "azure":
52
+ return [
53
+ process.env.BOSUN_VOICE_AZURE_ACCESS_TOKEN,
54
+ process.env.AZURE_OPENAI_ACCESS_TOKEN,
55
+ ];
56
+ case "claude":
57
+ return [
58
+ process.env.BOSUN_VOICE_CLAUDE_ACCESS_TOKEN,
59
+ process.env.ANTHROPIC_ACCESS_TOKEN,
60
+ ];
61
+ case "gemini":
62
+ return [
63
+ process.env.BOSUN_VOICE_GEMINI_ACCESS_TOKEN,
64
+ process.env.GEMINI_ACCESS_TOKEN,
65
+ process.env.GOOGLE_ACCESS_TOKEN,
66
+ ];
67
+ default:
68
+ return [];
69
+ }
70
+ }
71
+
72
+ function getStateTokenCandidates(provider, state) {
73
+ const byProvider = state?.providers?.[provider] || state?.[provider] || {};
74
+ return [
75
+ {
76
+ token: byProvider?.accessToken,
77
+ expiresAt: byProvider?.expiresAt,
78
+ source: "state",
79
+ },
80
+ {
81
+ token: byProvider?.access_token,
82
+ expiresAt: byProvider?.expires_at,
83
+ source: "state",
84
+ },
85
+ ];
86
+ }
87
+
88
+ export function resolveVoiceOAuthToken(provider, forceReload = false) {
89
+ const normalizedProvider = normalizeProvider(provider);
90
+ if (!normalizedProvider) return null;
91
+
92
+ const envToken = getProviderEnvCandidates(normalizedProvider)
93
+ .map((token) => String(token || "").trim())
94
+ .find(Boolean);
95
+ if (envToken) {
96
+ return {
97
+ token: envToken,
98
+ source: "env",
99
+ provider: normalizedProvider,
100
+ };
101
+ }
102
+
103
+ const state = getCachedState(forceReload);
104
+ const candidates = getStateTokenCandidates(normalizedProvider, state);
105
+ for (const candidate of candidates) {
106
+ const token = String(candidate?.token || "").trim();
107
+ if (!token) continue;
108
+ if (isExpired(candidate?.expiresAt)) continue;
109
+ return {
110
+ token,
111
+ source: candidate.source,
112
+ provider: normalizedProvider,
113
+ expiresAt: candidate?.expiresAt || null,
114
+ };
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ export function hasVoiceOAuthToken(provider, forceReload = false) {
121
+ return Boolean(resolveVoiceOAuthToken(provider, forceReload));
122
+ }
123
+
124
+ export function saveVoiceOAuthToken(provider, payload = {}) {
125
+ const normalizedProvider = normalizeProvider(provider);
126
+ if (!normalizedProvider) {
127
+ throw new Error("provider is required");
128
+ }
129
+
130
+ const token = String(payload?.accessToken || payload?.access_token || "").trim();
131
+ if (!token) {
132
+ throw new Error("access token is required");
133
+ }
134
+
135
+ const current = getCachedState(true);
136
+ const next = {
137
+ ...current,
138
+ providers: {
139
+ ...(current?.providers || {}),
140
+ [normalizedProvider]: {
141
+ accessToken: token,
142
+ expiresAt: payload?.expiresAt || payload?.expires_at || null,
143
+ refreshToken: payload?.refreshToken || payload?.refresh_token || null,
144
+ tokenType: payload?.tokenType || payload?.token_type || "Bearer",
145
+ updatedAt: new Date().toISOString(),
146
+ },
147
+ },
148
+ };
149
+
150
+ mkdirSync(dirname(VOICE_AUTH_STATE_PATH), { recursive: true });
151
+ writeFileSync(VOICE_AUTH_STATE_PATH, JSON.stringify(next, null, 2));
152
+ _cachedState = next;
153
+ _cachedStateAt = Date.now();
154
+
155
+ return {
156
+ ok: true,
157
+ path: VOICE_AUTH_STATE_PATH,
158
+ provider: normalizedProvider,
159
+ };
160
+ }
161
+
162
+ export function getVoiceAuthStatePath() {
163
+ return VOICE_AUTH_STATE_PATH;
164
+ }