bosun 0.41.0 → 0.41.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.
Files changed (64) hide show
  1. package/.env.example +8 -0
  2. package/README.md +20 -0
  3. package/agent/agent-event-bus.mjs +248 -6
  4. package/agent/agent-pool.mjs +125 -28
  5. package/agent/agent-work-analyzer.mjs +8 -16
  6. package/agent/retry-queue.mjs +164 -0
  7. package/bosun.config.example.json +25 -0
  8. package/bosun.schema.json +825 -183
  9. package/cli.mjs +59 -5
  10. package/config/config.mjs +130 -3
  11. package/infra/monitor.mjs +693 -67
  12. package/infra/runtime-accumulator.mjs +376 -84
  13. package/infra/session-tracker.mjs +82 -25
  14. package/lib/codebase-audit.mjs +133 -18
  15. package/package.json +23 -4
  16. package/server/setup-web-server.mjs +25 -0
  17. package/server/ui-server.mjs +248 -29
  18. package/setup.mjs +27 -24
  19. package/shell/codex-shell.mjs +34 -3
  20. package/shell/copilot-shell.mjs +50 -8
  21. package/task/msg-hub.mjs +193 -0
  22. package/task/pipeline.mjs +544 -0
  23. package/task/task-cli.mjs +38 -2
  24. package/task/task-executor-pipeline.mjs +143 -0
  25. package/task/task-executor.mjs +36 -27
  26. package/telegram/get-telegram-chat-id.mjs +57 -47
  27. package/ui/components/workspace-switcher.js +7 -7
  28. package/ui/demo-defaults.js +15694 -10573
  29. package/ui/modules/settings-schema.js +2 -0
  30. package/ui/modules/state.js +54 -57
  31. package/ui/modules/voice-client-sdk.js +375 -36
  32. package/ui/modules/voice-client.js +140 -31
  33. package/ui/setup.html +68 -2
  34. package/ui/styles/components.css +57 -0
  35. package/ui/styles.css +201 -1
  36. package/ui/tabs/dashboard.js +74 -0
  37. package/ui/tabs/logs.js +10 -0
  38. package/ui/tabs/settings.js +178 -99
  39. package/ui/tabs/tasks.js +31 -1
  40. package/ui/tabs/telemetry.js +34 -0
  41. package/ui/tabs/workflow-canvas-utils.mjs +8 -1
  42. package/ui/tabs/workflows.js +532 -275
  43. package/voice/voice-agents-sdk.mjs +1 -1
  44. package/voice/voice-relay.mjs +6 -6
  45. package/workflow/declarative-workflows.mjs +145 -0
  46. package/workflow/msg-hub.mjs +237 -0
  47. package/workflow/pipeline-workflows.mjs +287 -0
  48. package/workflow/pipeline.mjs +828 -315
  49. package/workflow/workflow-cli.mjs +128 -0
  50. package/workflow/workflow-engine.mjs +329 -17
  51. package/workflow/workflow-nodes/custom-loader.mjs +250 -0
  52. package/workflow/workflow-nodes.mjs +1955 -223
  53. package/workflow/workflow-templates.mjs +26 -8
  54. package/workflow-templates/agents.mjs +0 -1
  55. package/workflow-templates/bosun-native.mjs +212 -2
  56. package/workflow-templates/continuation-loop.mjs +339 -0
  57. package/workflow-templates/github.mjs +516 -40
  58. package/workflow-templates/planning.mjs +446 -17
  59. package/workflow-templates/reliability.mjs +65 -12
  60. package/workflow-templates/task-batch.mjs +24 -8
  61. package/workflow-templates/task-lifecycle.mjs +83 -6
  62. package/workspace/context-cache.mjs +66 -18
  63. package/workspace/workspace-manager.mjs +2 -1
  64. package/workflow-templates/issue-continuation.mjs +0 -243
@@ -19,6 +19,7 @@
19
19
  * @property {string} [unit] - Display unit (e.g., 'ms', 'min', 'sec')
20
20
  * @property {boolean} [restart] - If true, changing requires process restart
21
21
  * @property {boolean} [advanced] - If true, hidden unless "Show advanced" is on
22
+ * @property {string} [section] - Optional UI subsection label within a category
22
23
  */
23
24
 
24
25
  export const CATEGORIES = [
@@ -248,6 +249,7 @@ export const SETTINGS_SCHEMA = [
248
249
  { key: "SELF_RESTART_WATCH_ENABLED", label: "Self-Restart Watcher", category: "advanced", type: "boolean", description: "Auto-restart when source files change. Defaults to true in devmode." },
249
250
  { key: "MAX_PARALLEL", label: "Global Max Parallel", category: "advanced", type: "number", defaultVal: 6, min: 1, max: 50, description: "Global maximum parallel task slots across all executors." },
250
251
  { key: "RESTART_DELAY_MS", label: "Restart Delay", category: "advanced", type: "number", defaultVal: 10000, min: 1000, max: 60000, unit: "ms", description: "Delay before restarting after a crash." },
252
+ { key: "ENV_RELOAD_DELAY_MS", label: "Settings Reload Delay", category: "advanced", type: "number", defaultVal: 5000, min: 500, max: 60000, unit: "ms", description: "Quiet period before Bosun reloads runtime config after .env or settings changes. Higher values make restart-sensitive saves less abrupt." },
251
253
  { key: "SHARED_STATE_ENABLED", label: "Shared State", category: "advanced", type: "boolean", defaultVal: true, description: "Enable distributed task coordination for multi-instance setups." },
252
254
  { key: "WORKFLOW_AUTOMATION_ENABLED", label: "Workflow Automation", category: "advanced", type: "boolean", defaultVal: true, description: "Enable event-driven workflow auto-triggers from monitor events." },
253
255
  { key: "SHARED_STATE_STALE_THRESHOLD_MS", label: "Stale Threshold", category: "advanced", type: "number", defaultVal: 300000, min: 60000, max: 1800000, unit: "ms", description: "Time before a heartbeat is considered stale.", advanced: true },
@@ -54,6 +54,7 @@ const CACHE_TTL = {
54
54
  benchmarks: 8000,
55
55
  telemetry: 15000,
56
56
  analytics: 30000,
57
+ "retry-queue": 5000,
57
58
  };
58
59
 
59
60
  function _cacheKey(url) { return url; }
@@ -158,11 +159,31 @@ export const tasksStatusCounts = signal({ draft: 0, backlog: 0, inProgress: 0, i
158
159
  export const retryQueueData = signal({ count: 0, items: [] });
159
160
  export const retryQueueLoaded = signal(false);
160
161
 
162
+ function normalizeRetryQueuePayload(payload) {
163
+ return {
164
+ count: Number(payload?.count || 0),
165
+ items: Array.isArray(payload?.items) ? payload.items : [],
166
+ stats: payload?.stats && typeof payload.stats === "object"
167
+ ? {
168
+ totalRetriesToday: Number(payload.stats.totalRetriesToday || 0),
169
+ peakRetryDepth: Number(payload.stats.peakRetryDepth || 0),
170
+ exhaustedTaskIds: Array.isArray(payload.stats.exhaustedTaskIds) ? payload.stats.exhaustedTaskIds : [],
171
+ }
172
+ : {
173
+ totalRetriesToday: 0,
174
+ peakRetryDepth: 0,
175
+ exhaustedTaskIds: [],
176
+ },
177
+ };
178
+ }
179
+
161
180
  export async function loadRetryQueue() {
162
- const res = await apiFetch("/api/retry-queue", { _silent: true }).catch(() => ({ ok: false, items: [], count: 0 }));
163
- if (res?.ok) {
164
- retryQueueData.value = { count: res.count || 0, items: res.items || [] };
165
- }
181
+ const url = "/api/retry-queue";
182
+ if (_cacheFresh(url, "retry-queue")) return;
183
+ const res = await apiFetch(url, { _silent: true }).catch(() => ({ ok: false, items: [], count: 0, stats: null }));
184
+ retryQueueData.value = normalizeRetryQueuePayload(res);
185
+ _cacheSet(url, retryQueueData.value);
186
+ _markFresh("retry-queue");
166
187
  retryQueueLoaded.value = true;
167
188
  }
168
189
 
@@ -353,61 +374,18 @@ export function shouldShowToast(toast) {
353
374
  }
354
375
 
355
376
  /* ═══════════════════════════════════════════════════════════════
356
- * EXECUTOR DEFAULTS apply stored settings on first load
377
+ * LEGACY STORED DEFAULTS MIGRATION HOOK
357
378
  * ═══════════════════════════════════════════════════════════════ */
358
379
 
359
380
  let _defaultsApplied = false;
360
381
 
361
382
  /**
362
- * Read stored executor defaults from CloudStorage and POST them to
363
- * the server if they differ from the current config.
364
- * Only runs once per app lifecycle (not on tab switches).
383
+ * Reserved for one-time client preference migrations.
384
+ * Executor runtime defaults now live only in Server Config.
365
385
  */
366
386
  export async function applyStoredDefaults() {
367
387
  if (_defaultsApplied) return;
368
388
  _defaultsApplied = true;
369
-
370
- const [maxP, sdk, region] = await Promise.all([
371
- _cloudGet("defaultMaxParallel"),
372
- _cloudGet("defaultSdk"),
373
- _cloudGet("defaultRegion"),
374
- ]);
375
-
376
- const promises = [];
377
-
378
- if (maxP != null) {
379
- const current = executorData.value;
380
- const currentMax =
381
- current?.data?.maxParallel ??
382
- current?.maxParallel ??
383
- null;
384
- const isPaused = Boolean(current?.paused || current?.data?.paused);
385
- if (!isPaused && currentMax !== maxP) {
386
- promises.push(
387
- apiFetch("/api/executor/maxparallel", {
388
- method: "POST",
389
- body: JSON.stringify({ maxParallel: maxP }),
390
- _silent: true,
391
- }).catch(() => {}),
392
- );
393
- }
394
- }
395
-
396
- const settingsUpdates = {};
397
- if (sdk && sdk !== "auto") settingsUpdates.INTERNAL_EXECUTOR_SDK = sdk;
398
- if (region && region !== "auto") settingsUpdates.EXECUTOR_REGIONS = region;
399
-
400
- if (Object.keys(settingsUpdates).length) {
401
- promises.push(
402
- apiFetch("/api/settings/update", {
403
- method: "POST",
404
- body: JSON.stringify({ changes: settingsUpdates }),
405
- _silent: true,
406
- }).catch(() => {}),
407
- );
408
- }
409
-
410
- if (promises.length) await Promise.all(promises);
411
389
  }
412
390
 
413
391
  /* ═══════════════════════════════════════════════════════════════
@@ -667,9 +645,10 @@ export async function loadInfra() {
667
645
  }
668
646
 
669
647
  /** Load system logs → logsData */
670
- export async function loadLogs() {
648
+ export async function loadLogs(options = {}) {
671
649
  const url = `/api/logs?lines=${logsLines.value}`;
672
- if (_cacheFresh(url, "logs")) return;
650
+ const force = Boolean(options?.force);
651
+ if (!force && _cacheFresh(url, "logs")) return;
673
652
  const res = await apiFetch(url, { _silent: true }).catch(() => ({ data: null }));
674
653
  logsData.value = res.data ?? res ?? null;
675
654
  _cacheSet(url, logsData.value);
@@ -709,7 +688,7 @@ export async function loadAgentLogFileList() {
709
688
  }
710
689
 
711
690
  /** Load tail of the currently selected agent log → agentLogTail */
712
- export async function loadAgentLogTailData() {
691
+ export async function loadAgentLogTailData(options = {}) {
713
692
  if (!agentLogFile.value) {
714
693
  agentLogTail.value = null;
715
694
  return;
@@ -718,10 +697,20 @@ export async function loadAgentLogTailData() {
718
697
  file: agentLogFile.value,
719
698
  lines: String(agentLogLines.value),
720
699
  });
721
- const res = await apiFetch(`/api/agent-logs/tail?${params}`, {
700
+ const url = `/api/agent-logs/tail?${params}`;
701
+ if (!options?.force && _cacheFresh(url, "logs")) {
702
+ const cached = _cacheGet(url);
703
+ if (cached) {
704
+ agentLogTail.value = cached.data;
705
+ return;
706
+ }
707
+ }
708
+ const res = await apiFetch(url, {
722
709
  _silent: true,
723
710
  }).catch(() => ({ data: null }));
724
711
  agentLogTail.value = res.data ?? res ?? null;
712
+ _cacheSet(url, agentLogTail.value);
713
+ _markFresh("logs");
725
714
  }
726
715
 
727
716
  /**
@@ -884,7 +873,7 @@ export async function loadBenchmarks(providerId = "") {
884
873
 
885
874
  const TAB_LOADERS = {
886
875
  dashboard: () =>
887
- Promise.all([loadStatus(), loadExecutor(), loadProjectSummary()]),
876
+ Promise.all([loadStatus(), loadExecutor(), loadProjectSummary(), loadRetryQueue()]),
888
877
  tasks: () => loadTasks(),
889
878
  benchmarks: () => loadBenchmarks(),
890
879
  agents: () => Promise.all([loadAgents(), loadExecutor(), import("../components/session-list.js").then((m) => m.loadSessions()).catch(() => {})]),
@@ -905,6 +894,7 @@ const TAB_LOADERS = {
905
894
  loadTelemetryExecutors(),
906
895
  loadTelemetryAlerts(),
907
896
  loadUsageAnalytics(30),
897
+ loadRetryQueue(),
908
898
  ]),
909
899
  settings: () => Promise.all([loadStatus(), loadConfig()]),
910
900
  };
@@ -990,20 +980,27 @@ export function scheduleRefresh(ms = 5000) {
990
980
  /* ─── WebSocket invalidation listener ─── */
991
981
 
992
982
  const WS_CHANNEL_MAP = {
993
- dashboard: ["overview", "executor", "tasks", "agents"],
983
+ dashboard: ["overview", "executor", "tasks", "agents", "retry-queue"],
994
984
  tasks: ["tasks"],
995
985
  benchmarks: ["benchmarks", "tasks", "executor", "workflows", "workspaces", "library"],
996
986
  agents: ["agents", "executor"],
997
987
  infra: ["worktrees", "workspaces", "presence"],
998
988
  control: ["executor", "overview"],
999
989
  logs: ["*"],
1000
- telemetry: ["*"],
990
+ telemetry: ["*", "retry-queue"],
1001
991
  settings: ["overview"],
1002
992
  };
1003
993
 
1004
994
  /** Start listening for WS invalidation messages and auto-refreshing. */
1005
995
  export function initWsInvalidationListener() {
1006
996
  onWsMessage((msg) => {
997
+ if (msg?.type === "retry-queue-updated") {
998
+ retryQueueData.value = normalizeRetryQueuePayload(msg?.payload);
999
+ _cacheSet("/api/retry-queue", retryQueueData.value);
1000
+ _markFresh("retry-queue");
1001
+ retryQueueLoaded.value = true;
1002
+ return;
1003
+ }
1007
1004
  if (msg?.type !== "invalidate") return;
1008
1005
  const channels = Array.isArray(msg.channels) ? msg.channels : [];
1009
1006
  // Clear cache for invalidated channels so next fetch is fresh