bosun 0.40.21 → 0.41.1

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 (80) hide show
  1. package/.env.example +8 -0
  2. package/README.md +20 -0
  3. package/agent/agent-custom-tools.mjs +23 -5
  4. package/agent/agent-event-bus.mjs +248 -6
  5. package/agent/agent-pool.mjs +131 -30
  6. package/agent/agent-work-analyzer.mjs +8 -16
  7. package/agent/primary-agent.mjs +81 -7
  8. package/agent/retry-queue.mjs +164 -0
  9. package/bench/swebench/bosun-swebench.mjs +5 -0
  10. package/bosun.config.example.json +25 -0
  11. package/bosun.schema.json +825 -183
  12. package/cli.mjs +267 -8
  13. package/config/config-doctor.mjs +51 -2
  14. package/config/config.mjs +232 -5
  15. package/github/github-auth-manager.mjs +70 -19
  16. package/infra/library-manager.mjs +894 -60
  17. package/infra/monitor.mjs +701 -69
  18. package/infra/runtime-accumulator.mjs +376 -84
  19. package/infra/session-tracker.mjs +95 -28
  20. package/infra/test-runtime.mjs +267 -0
  21. package/lib/codebase-audit.mjs +133 -18
  22. package/package.json +30 -8
  23. package/server/setup-web-server.mjs +29 -1
  24. package/server/ui-server.mjs +1571 -49
  25. package/setup.mjs +27 -24
  26. package/shell/codex-shell.mjs +34 -3
  27. package/shell/copilot-shell.mjs +50 -8
  28. package/task/msg-hub.mjs +193 -0
  29. package/task/pipeline.mjs +544 -0
  30. package/task/task-claims.mjs +6 -10
  31. package/task/task-cli.mjs +38 -2
  32. package/task/task-executor-pipeline.mjs +143 -0
  33. package/task/task-executor.mjs +36 -27
  34. package/telegram/get-telegram-chat-id.mjs +57 -47
  35. package/ui/components/chat-view.js +18 -1
  36. package/ui/components/workspace-switcher.js +321 -9
  37. package/ui/demo-defaults.js +17830 -10433
  38. package/ui/demo.html +9 -1
  39. package/ui/modules/router.js +1 -1
  40. package/ui/modules/settings-schema.js +2 -0
  41. package/ui/modules/state.js +54 -57
  42. package/ui/modules/voice-client-sdk.js +376 -37
  43. package/ui/modules/voice-client.js +173 -33
  44. package/ui/setup.html +68 -2
  45. package/ui/styles/components.css +571 -1
  46. package/ui/styles.css +201 -1
  47. package/ui/tabs/dashboard.js +74 -0
  48. package/ui/tabs/library.js +410 -55
  49. package/ui/tabs/logs.js +10 -0
  50. package/ui/tabs/settings.js +178 -99
  51. package/ui/tabs/tasks.js +1083 -507
  52. package/ui/tabs/telemetry.js +34 -0
  53. package/ui/tabs/workflow-canvas-utils.mjs +38 -1
  54. package/ui/tabs/workflows.js +1275 -402
  55. package/voice/voice-agents-sdk.mjs +2 -2
  56. package/voice/voice-relay.mjs +28 -20
  57. package/workflow/declarative-workflows.mjs +145 -0
  58. package/workflow/msg-hub.mjs +237 -0
  59. package/workflow/pipeline-workflows.mjs +287 -0
  60. package/workflow/pipeline.mjs +828 -315
  61. package/workflow/project-detection.mjs +559 -0
  62. package/workflow/workflow-cli.mjs +128 -0
  63. package/workflow/workflow-contract.mjs +433 -232
  64. package/workflow/workflow-engine.mjs +510 -47
  65. package/workflow/workflow-nodes/custom-loader.mjs +251 -0
  66. package/workflow/workflow-nodes.mjs +2024 -184
  67. package/workflow/workflow-templates.mjs +118 -24
  68. package/workflow-templates/agents.mjs +20 -20
  69. package/workflow-templates/bosun-native.mjs +212 -2
  70. package/workflow-templates/code-quality.mjs +20 -14
  71. package/workflow-templates/continuation-loop.mjs +339 -0
  72. package/workflow-templates/github.mjs +516 -40
  73. package/workflow-templates/planning.mjs +446 -17
  74. package/workflow-templates/reliability.mjs +65 -12
  75. package/workflow-templates/task-batch.mjs +27 -10
  76. package/workflow-templates/task-execution.mjs +752 -0
  77. package/workflow-templates/task-lifecycle.mjs +117 -14
  78. package/workspace/context-cache.mjs +66 -18
  79. package/workspace/workspace-manager.mjs +153 -1
  80. package/workflow-templates/issue-continuation.mjs +0 -243
@@ -21,6 +21,8 @@ import {
21
21
  List, ListItem, ListItemText, ListItemIcon,
22
22
  ListItemSecondaryAction, Divider, Tooltip, Alert,
23
23
  Menu, MenuItem,
24
+ ToggleButton, ToggleButtonGroup,
25
+ Slider, Collapse,
24
26
  } from "@mui/material";
25
27
 
26
28
  const html = htm.bind(h);
@@ -146,6 +148,35 @@ async function scanDisk() {
146
148
  return res;
147
149
  }
148
150
 
151
+ // ─── API helpers for workspace state management ────────────
152
+
153
+ async function setWorkspaceState(workspaceId, state) {
154
+ const res = await apiFetch("/api/workspaces/state", {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: JSON.stringify({ workspaceId, state }),
158
+ });
159
+ if (res?.ok) await loadWorkspaces();
160
+ return res;
161
+ }
162
+
163
+ async function setWorkspaceExecutors(workspaceId, executors) {
164
+ const res = await apiFetch("/api/workspaces/executors", {
165
+ method: "POST",
166
+ headers: { "Content-Type": "application/json" },
167
+ body: JSON.stringify({ workspaceId, ...executors }),
168
+ });
169
+ if (res?.ok) await loadWorkspaces();
170
+ return res;
171
+ }
172
+
173
+ // State display helpers
174
+ const STATE_CONFIG = {
175
+ active: { icon: "●", color: "#10b981", label: "Active", desc: "Workflows running, executors available" },
176
+ paused: { icon: "◐", color: "#f59e0b", label: "Paused", desc: "In-flight tasks finish, no new starts" },
177
+ disabled: { icon: "○", color: "#71717a", label: "Disabled", desc: "Fully off — no workflows, no executors" },
178
+ };
179
+
149
180
  // ─── Confirm dialog helper ─────────────────────────────────
150
181
  function ConfirmBar({ message, onConfirm, onCancel, loading }) {
151
182
  return html`
@@ -317,6 +348,208 @@ function AddRepoForm({ workspaceId }) {
317
348
  `;
318
349
  }
319
350
 
351
+ // ─── Workspace state toggle ─────────────────────────────────
352
+ function WorkspaceStateToggle({ ws, compact = false }) {
353
+ const [saving, setSaving] = useState(false);
354
+ const currentState = ws.state || "active";
355
+
356
+ const handleChange = useCallback(async (_e, newState) => {
357
+ if (!newState || newState === currentState) return;
358
+ setSaving(true);
359
+ haptic("medium");
360
+ try {
361
+ await setWorkspaceState(ws.id, newState);
362
+ } catch (e) {
363
+ console.warn("[ws-manager] state change error:", e);
364
+ } finally {
365
+ setSaving(false);
366
+ }
367
+ }, [ws.id, currentState]);
368
+
369
+ if (compact) {
370
+ const cfg = STATE_CONFIG[currentState];
371
+ return html`
372
+ <${Tooltip} title="${cfg.label}: ${cfg.desc}">
373
+ <span style="color: ${cfg.color}; font-size: 14px; cursor: default;">${cfg.icon}</span>
374
+ <//>
375
+ `;
376
+ }
377
+
378
+ return html`
379
+ <${Stack} direction="row" spacing=${1} alignItems="center">
380
+ <${ToggleButtonGroup}
381
+ value=${currentState}
382
+ exclusive
383
+ onChange=${handleChange}
384
+ size="small"
385
+ disabled=${saving}
386
+ sx=${{ height: 30 }}
387
+ >
388
+ ${Object.entries(STATE_CONFIG).map(([key, cfg]) => html`
389
+ <${ToggleButton}
390
+ key=${key}
391
+ value=${key}
392
+ sx=${{
393
+ px: 1.2,
394
+ py: 0.3,
395
+ fontSize: "11px",
396
+ textTransform: "none",
397
+ fontWeight: currentState === key ? 600 : 400,
398
+ color: currentState === key ? cfg.color : "text.secondary",
399
+ borderColor: currentState === key ? cfg.color : undefined,
400
+ "&.Mui-selected": {
401
+ backgroundColor: cfg.color + "18",
402
+ color: cfg.color,
403
+ borderColor: cfg.color + "60",
404
+ "&:hover": { backgroundColor: cfg.color + "28" },
405
+ },
406
+ }}
407
+ >
408
+ <${Tooltip} title=${cfg.desc}>
409
+ <span>${cfg.icon} ${cfg.label}</span>
410
+ <//>
411
+ <//>
412
+ `)}
413
+ <//>
414
+ ${saving && html`<${CircularProgress} size=${14} />`}
415
+ <//>
416
+ `;
417
+ }
418
+
419
+ // ─── Executor config panel (collapsible) ────────────────────
420
+ function ExecutorConfigPanel({ ws }) {
421
+ const [expanded, setExpanded] = useState(false);
422
+ const [saving, setSaving] = useState(false);
423
+ const execs = ws.executors || {};
424
+ const [maxConcurrent, setMaxConcurrent] = useState(execs.maxConcurrent || 3);
425
+ const [pool, setPool] = useState(execs.pool || "shared");
426
+ const [weight, setWeight] = useState(execs.weight || 1.0);
427
+
428
+ // Sync state with ws props when they change
429
+ useEffect(() => {
430
+ const e = ws.executors || {};
431
+ setMaxConcurrent(e.maxConcurrent || 3);
432
+ setPool(e.pool || "shared");
433
+ setWeight(e.weight || 1.0);
434
+ }, [ws.executors?.maxConcurrent, ws.executors?.pool, ws.executors?.weight]);
435
+
436
+ const handleSave = useCallback(async () => {
437
+ setSaving(true);
438
+ haptic("medium");
439
+ try {
440
+ await setWorkspaceExecutors(ws.id, { maxConcurrent, pool, weight });
441
+ } catch (e) {
442
+ console.warn("[ws-manager] executor config error:", e);
443
+ } finally {
444
+ setSaving(false);
445
+ }
446
+ }, [ws.id, maxConcurrent, pool, weight]);
447
+
448
+ const hasChanges = maxConcurrent !== (execs.maxConcurrent || 3)
449
+ || pool !== (execs.pool || "shared")
450
+ || weight !== (execs.weight || 1.0);
451
+
452
+ return html`
453
+ <${Box} sx=${{ mt: 0.5 }}>
454
+ <${Button}
455
+ size="small"
456
+ variant="text"
457
+ onClick=${() => { haptic("light"); setExpanded(!expanded); }}
458
+ sx=${{ textTransform: "none", fontSize: "11px", color: "text.secondary", px: 0.5 }}
459
+ >
460
+ ${resolveIcon("settings")} ${" "}Executors ${expanded ? "▾" : "▸"}
461
+ ${!expanded && html`
462
+ <${Chip}
463
+ label="${execs.maxConcurrent || 3} slots · ${execs.pool || "shared"}"
464
+ size="small"
465
+ variant="outlined"
466
+ sx=${{ ml: 0.5, height: 18, fontSize: "10px" }}
467
+ />
468
+ `}
469
+ <//>
470
+
471
+ <${Collapse} in=${expanded}>
472
+ <${Stack} spacing=${1.5} sx=${{ pt: 1, pb: 0.5, px: 0.5 }}>
473
+
474
+ <${Box}>
475
+ <${Typography} variant="caption" color="text.secondary" sx=${{ mb: 0.5, display: "block" }}>
476
+ Max Concurrent Executors: ${maxConcurrent}
477
+ <//>
478
+ <${Slider}
479
+ value=${maxConcurrent}
480
+ onChange=${(_e, v) => setMaxConcurrent(v)}
481
+ min=${1}
482
+ max=${10}
483
+ step=${1}
484
+ marks=${[
485
+ { value: 1, label: "1" },
486
+ { value: 3, label: "3" },
487
+ { value: 5, label: "5" },
488
+ { value: 10, label: "10" },
489
+ ]}
490
+ size="small"
491
+ sx=${{ maxWidth: 240 }}
492
+ />
493
+ <//>
494
+
495
+
496
+ <${Stack} direction="row" spacing=${1} alignItems="center">
497
+ <${Typography} variant="caption" color="text.secondary">Pool:<//>
498
+ <${ToggleButtonGroup}
499
+ value=${pool}
500
+ exclusive
501
+ onChange=${(_e, v) => { if (v) setPool(v); }}
502
+ size="small"
503
+ sx=${{ height: 26 }}
504
+ >
505
+ <${ToggleButton} value="shared" sx=${{ px: 1, fontSize: "11px", textTransform: "none" }}>
506
+ <${Tooltip} title="Shares executor capacity across workspaces">
507
+ <span>Shared</span>
508
+ <//>
509
+ <//>
510
+ <${ToggleButton} value="dedicated" sx=${{ px: 1, fontSize: "11px", textTransform: "none" }}>
511
+ <${Tooltip} title="Dedicated executor pool — isolated from other workspaces">
512
+ <span>Dedicated</span>
513
+ <//>
514
+ <//>
515
+ <//>
516
+ <//>
517
+
518
+
519
+ ${pool === "shared" && html`
520
+ <${Box}>
521
+ <${Typography} variant="caption" color="text.secondary" sx=${{ mb: 0.5, display: "block" }}>
522
+ Priority Weight: ${weight.toFixed(1)}×
523
+ <//>
524
+ <${Slider}
525
+ value=${weight}
526
+ onChange=${(_e, v) => setWeight(v)}
527
+ min=${0.1}
528
+ max=${5.0}
529
+ step=${0.1}
530
+ size="small"
531
+ sx=${{ maxWidth: 200 }}
532
+ />
533
+ <//>
534
+ `}
535
+
536
+
537
+ ${hasChanges && html`
538
+ <${Button}
539
+ size="small"
540
+ variant="contained"
541
+ onClick=${handleSave}
542
+ disabled=${saving}
543
+ startIcon=${saving ? html`<${CircularProgress} size=${14} />` : null}
544
+ sx=${{ alignSelf: "flex-start", textTransform: "none", fontSize: "12px" }}
545
+ >${saving ? "Saving…" : "Save Executor Config"}<//>
546
+ `}
547
+ <//>
548
+ <//>
549
+ <//>
550
+ `;
551
+ }
552
+
320
553
  // ─── Single workspace card in the management panel ─────────
321
554
  function WorkspaceCard({ ws }) {
322
555
  const isActive = ws.id === activeWorkspaceId.value;
@@ -324,6 +557,8 @@ function WorkspaceCard({ ws }) {
324
557
  const [delConfirm, setDelConfirm] = useState(false);
325
558
  const [deleting, setDeleting] = useState(false);
326
559
  const [activating, setActivating] = useState(false);
560
+ const wsState = ws.state || "active";
561
+ const stateCfg = STATE_CONFIG[wsState];
327
562
 
328
563
  const handleSetActive = useCallback(async () => {
329
564
  setActivating(true);
@@ -365,19 +600,43 @@ function WorkspaceCard({ ws }) {
365
600
  variant="outlined"
366
601
  sx=${{
367
602
  mb: 2,
368
- borderColor: isActive ? "primary.main" : "divider",
603
+ borderColor: wsState === "disabled" ? "action.disabled"
604
+ : isActive ? "primary.main" : "divider",
369
605
  borderWidth: isActive ? 2 : 1,
606
+ opacity: wsState === "disabled" ? 0.6 : 1,
607
+ transition: "opacity 0.2s, border-color 0.2s",
370
608
  }}
371
609
  >
372
610
  <${CardContent} sx=${{ pb: 0 }}>
373
611
  <${Stack} direction="row" justifyContent="space-between" alignItems="center">
374
612
  <${Stack} direction="row" spacing=${1} alignItems="center">
613
+ <${Tooltip} title="${stateCfg.label}: ${stateCfg.desc}">
614
+ <span style="color: ${stateCfg.color}; font-size: 16px;">${stateCfg.icon}</span>
615
+ <//>
375
616
  <${Typography} variant="subtitle1" fontWeight="bold">${ws.name}<//>
376
617
  ${isActive && html`
377
618
  <${Chip} label="Active" size="small" color="primary" />
378
619
  `}
620
+ ${wsState === "paused" && html`
621
+ <${Chip} label="Paused" size="small"
622
+ sx=${{ bgcolor: "#f59e0b22", color: "#f59e0b", fontWeight: 600, fontSize: "10px" }}
623
+ />
624
+ `}
625
+ ${wsState === "disabled" && html`
626
+ <${Chip} label="Disabled" size="small"
627
+ sx=${{ bgcolor: "#71717a22", color: "#71717a", fontWeight: 600, fontSize: "10px" }}
628
+ />
629
+ `}
379
630
  <//>
380
631
  <//>
632
+
633
+
634
+ <${Box} sx=${{ mt: 1.5, mb: 0.5 }}>
635
+ <${WorkspaceStateToggle} ws=${ws} />
636
+ <//>
637
+
638
+
639
+ <${ExecutorConfigPanel} ws=${ws} />
381
640
  <//>
382
641
 
383
642
  <${CardActions} sx=${{ justifyContent: "flex-end", pt: 0.5 }}>
@@ -512,8 +771,37 @@ export function WorkspaceManager({ open, onClose }) {
512
771
  const wsList = workspaces.value;
513
772
  const loading = workspacesLoading.value;
514
773
 
774
+ // Compute state summary counts
775
+ const stateCounts = { active: 0, paused: 0, disabled: 0 };
776
+ wsList.forEach((ws) => {
777
+ const s = ws.state || "active";
778
+ if (stateCounts[s] !== undefined) stateCounts[s]++;
779
+ });
780
+
515
781
  return html`
516
782
  <${Modal} title="Manage Workspaces" open=${open} onClose=${onClose}>
783
+
784
+ ${wsList.length > 0 && html`
785
+ <${Stack} direction="row" spacing=${1.5} sx=${{ mb: 2 }} alignItems="center">
786
+ ${Object.entries(STATE_CONFIG).map(([key, cfg]) => html`
787
+ <${Chip}
788
+ key=${key}
789
+ icon=${html`<span style="color: ${cfg.color}; font-size: 12px; margin-left: 8px;">${cfg.icon}</span>`}
790
+ label="${stateCounts[key] || 0} ${cfg.label}"
791
+ size="small"
792
+ variant=${stateCounts[key] > 0 ? "filled" : "outlined"}
793
+ sx=${{
794
+ fontSize: "11px",
795
+ fontWeight: 500,
796
+ bgcolor: stateCounts[key] > 0 ? cfg.color + "18" : undefined,
797
+ borderColor: cfg.color + "40",
798
+ color: stateCounts[key] > 0 ? cfg.color : "text.secondary",
799
+ }}
800
+ />
801
+ `)}
802
+ <//>
803
+ `}
804
+
517
805
  <${Box} sx=${{ mb: 2 }}>
518
806
  <${Button}
519
807
  size="small"
@@ -654,7 +942,11 @@ export function WorkspaceSwitcher() {
654
942
  >
655
943
  ${switchingId
656
944
  ? html`<${CircularProgress} size=${16} sx=${{ mr: 1 }} />`
657
- : null
945
+ : currentWs
946
+ ? html`<span style="color: ${STATE_CONFIG[currentWs.state || "active"].color}; margin-right: 4px;">
947
+ ${STATE_CONFIG[currentWs.state || "active"].icon}
948
+ </span>`
949
+ : null
658
950
  }
659
951
  ${currentWs?.name || currentId || "Select Workspace"}
660
952
  <//>
@@ -664,13 +956,33 @@ export function WorkspaceSwitcher() {
664
956
  open=${Boolean(menuAnchor)}
665
957
  onClose=${handleMenuClose}
666
958
  >
667
- ${wsList.map((ws) => html`
668
- <${MenuItem}
669
- key=${ws.id}
670
- selected=${ws.id === currentId}
671
- onClick=${() => handleSelect(ws.id)}
672
- >${ws.name || ws.id}<//>
673
- `)}
959
+ ${wsList.map((ws) => {
960
+ const st = STATE_CONFIG[ws.state || "active"];
961
+ return html`
962
+ <${MenuItem}
963
+ key=${ws.id}
964
+ selected=${ws.id === currentId}
965
+ onClick=${() => handleSelect(ws.id)}
966
+ sx=${{
967
+ opacity: ws.state === "disabled" ? 0.5 : 1,
968
+ gap: 1,
969
+ }}
970
+ >
971
+ <span style="color: ${st.color}; font-size: 12px; width: 16px; text-align: center;">${st.icon}</span>
972
+ ${ws.name || ws.id}
973
+ ${ws.state === "paused" && html`
974
+ <${Chip} label="paused" size="small"
975
+ sx=${{ ml: 0.5, height: 16, fontSize: "9px", bgcolor: "#f59e0b22", color: "#f59e0b" }}
976
+ />
977
+ `}
978
+ ${ws.state === "disabled" && html`
979
+ <${Chip} label="off" size="small"
980
+ sx=${{ ml: 0.5, height: 16, fontSize: "9px", bgcolor: "#71717a22", color: "#71717a" }}
981
+ />
982
+ `}
983
+ <//>
984
+ `;
985
+ })}
674
986
  <${Divider} />
675
987
  <${MenuItem} onClick=${() => handleSelect("__manage__")}>
676
988
  Manage Workspaces