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
@@ -151,11 +151,15 @@ After completing your implementation:
151
151
  }, { x: 200, y: 1200 }),
152
152
 
153
153
  node("notify-failure", "notify.telegram", "Notify Review Failed", {
154
- message: ":close: Frontend task **{{taskTitle}}** failed visual verification. Review evidence in .bosun/evidence/",
154
+ message: " Frontend task **{{taskTitle}}** failed visual verification.\n" +
155
+ "Evidence dir: `{{evidenceDir}}`\n" +
156
+ "Reason: `{{model-review.reason}}`\n" +
157
+ "Evidence count: `{{model-review.evidenceCount}}`\n" +
158
+ "Review output: {{model-review.reviewOutput}}",
155
159
  }, { x: 600, y: 1080 }),
156
160
 
157
161
  node("log-failure", "notify.log", "Log Failure", {
158
- message: "Frontend verification failed for {{taskId}}: review output in evidence dir",
162
+ message: "Frontend verification failed for {{taskId}}: evidenceDir={{evidenceDir}} reason={{model-review.reason}} evidenceCount={{model-review.evidenceCount}}",
159
163
  level: "warn",
160
164
  }, { x: 600, y: 1200 }),
161
165
  ],
@@ -3,10 +3,12 @@
3
3
  *
4
4
  * Templates:
5
5
  * - PR Merge Strategy (recommended)
6
- * - PR Triage & Labels
7
- * - PR Conflict Resolver (recommended)
8
- * - Stale PR Reaper
6
+ * - PR Triage & Labels (recommended)
7
+ * - PR Conflict Resolver (superseded by Watchdog)
8
+ * - Stale PR Reaper (recommended)
9
9
  * - Release Drafter
10
+ * - Bosun PR Watchdog (recommended — replaces pr-cleanup-daemon.mjs)
11
+ * - GitHub ↔ Kanban Sync (recommended — replaces github-reconciler.mjs)
10
12
  */
11
13
 
12
14
  import { node, edge, resetLayout } from "./_helpers.mjs";
@@ -208,6 +210,7 @@ export const PR_TRIAGE_TEMPLATE = {
208
210
  "changes, add labels, and assign reviewers based on CODEOWNERS.",
209
211
  category: "github",
210
212
  enabled: true,
213
+ recommended: true,
211
214
  trigger: "trigger.pr_event",
212
215
  variables: {
213
216
  smallThreshold: 50,
@@ -472,7 +475,8 @@ export const STALE_PR_REAPER_TEMPLATE = {
472
475
  "Close stale PRs that have been inactive for too long. Posts a " +
473
476
  "warning comment before closing and cleans up associated branches.",
474
477
  category: "github",
475
- enabled: false,
478
+ enabled: true,
479
+ recommended: true,
476
480
  trigger: "trigger.schedule",
477
481
  variables: {
478
482
  staleAfterDays: 14,
@@ -493,12 +497,12 @@ export const STALE_PR_REAPER_TEMPLATE = {
493
497
  }, { x: 400, y: 350 }),
494
498
 
495
499
  node("warn-stale", "action.run_command", "Post Warning Comment", {
496
- command: "echo 'Would warn stale PRs {{warningBeforeDays}} days before stale threshold ({{staleAfterDays}} days)'",
500
+ command: "node -e \"const {execFileSync}=require('child_process');const stale=Number('{{staleAfterDays}}')||14;const warn=Number('{{warningBeforeDays}}')||3;const now=Date.now();const prs=JSON.parse(execFileSync('gh',['pr','list','--state','open','--json','number,updatedAt,labels','--limit','100'],{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim()||'[]');let n=0;for(const pr of prs){const age=(now-new Date(pr.updatedAt))/864e5;if(age>=(stale-warn)&&age<stale){const lbl=(pr.labels||[]).some(l=>(typeof l==='string'?l:l?.name)==='stale-warning');if(!lbl){try{execFileSync('gh',['pr','comment',String(pr.number),'--body','\\u26a0\\ufe0f This PR has been inactive for '+Math.floor(age)+' day(s) and will be closed in '+Math.ceil(stale-age)+' day(s). Please update or close it if no longer needed.'],{encoding:'utf8',stdio:['pipe','pipe','pipe']});execFileSync('gh',['pr','edit',String(pr.number),'--add-label','stale-warning'],{encoding:'utf8',stdio:['pipe','pipe','pipe']});n++;}catch(e){}}}}console.log('Warned '+n+' PR(s).');\"",
497
501
  continueOnError: true,
498
502
  }, { x: 200, y: 500 }),
499
503
 
500
504
  node("close-stale", "action.run_command", "Close Expired PRs", {
501
- command: "echo 'Would close PRs inactive > {{staleAfterDays}} days'",
505
+ command: "node -e \"const {execFileSync}=require('child_process');const stale=Number('{{staleAfterDays}}')||14;const now=Date.now();const prs=JSON.parse(execFileSync('gh',['pr','list','--state','open','--json','number,title,updatedAt,headRefName','--limit','100'],{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim()||'[]');let n=0;for(const pr of prs){const age=(now-new Date(pr.updatedAt))/864e5;if(age>=stale){try{execFileSync('gh',['pr','close',String(pr.number),'--comment','Automatically closed: inactive for '+Math.floor(age)+' days (threshold: '+stale+' days).','--delete-branch'],{encoding:'utf8',stdio:['pipe','pipe','pipe']});n++;}catch(e){process.stderr.write('close #'+pr.number+': '+(e?.message||e)+'\\n');}}}console.log('Closed '+n+' stale PR(s).');\"",
502
506
  continueOnError: true,
503
507
  }, { x: 200, y: 650 }),
504
508
 
@@ -506,10 +510,15 @@ export const STALE_PR_REAPER_TEMPLATE = {
506
510
  command: "git fetch --prune origin",
507
511
  }, { x: 200, y: 800 }),
508
512
 
513
+ node("prune-worktrees", "action.run_command", "Prune Git Worktrees", {
514
+ command: "git worktree prune --expire 7.days.ago 2>/dev/null && echo 'Worktree prune complete.' || echo 'Worktree prune skipped (not a git repo).'",
515
+ continueOnError: true,
516
+ }, { x: 200, y: 950 }),
517
+
509
518
  node("summary", "notify.telegram", "Summary", {
510
519
  message: ":trash: Stale PR cleanup complete",
511
520
  silent: true,
512
- }, { x: 200, y: 950 }),
521
+ }, { x: 200, y: 1100 }),
513
522
 
514
523
  node("skip", "notify.log", "No Stale PRs", {
515
524
  message: "No stale PRs found",
@@ -523,7 +532,8 @@ export const STALE_PR_REAPER_TEMPLATE = {
523
532
  edge("has-stale", "skip", { condition: "$output?.result !== true" }),
524
533
  edge("warn-stale", "close-stale"),
525
534
  edge("close-stale", "cleanup-branches"),
526
- edge("cleanup-branches", "summary"),
535
+ edge("cleanup-branches", "prune-worktrees"),
536
+ edge("prune-worktrees", "summary"),
527
537
  ],
528
538
  metadata: {
529
539
  author: "bosun",
@@ -535,9 +545,11 @@ export const STALE_PR_REAPER_TEMPLATE = {
535
545
  module: "workspace-reaper.mjs",
536
546
  functions: ["runReaperSweep", "cleanOrphanedWorktrees"],
537
547
  calledFrom: ["monitor.mjs:runMaintenanceSweep"],
538
- description: "Replaces scattered stale PR and branch cleanup logic with " +
539
- "a structured workflow. Warning, closing, and branch deletion are " +
540
- "explicit, auditable steps.",
548
+ description:
549
+ "Replaces workspace-reaper.mjs stale PR / orphaned worktree cleanup and " +
550
+ "the pr-cleanup-daemon.mjs temporary worktree remnants. Warning, closing, " +
551
+ "branch deletion, and worktree pruning are explicit, auditable steps.",
552
+ also: ["pr-cleanup-daemon.mjs (temp worktree cleanup)"],
541
553
  },
542
554
  },
543
555
  };
@@ -676,7 +688,8 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
676
688
  "merge — preventing destructive PRs (e.g. -183k lines) from being silently " +
677
689
  "auto-merged. External-contributor PRs without bosun-attached are never touched.",
678
690
  category: "github",
679
- enabled: false,
691
+ enabled: true,
692
+ recommended: true,
680
693
  trigger: "trigger.schedule",
681
694
  variables: {
682
695
  mergeMethod: "squash", // squash | merge | rebase
@@ -1005,3 +1018,132 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
1005
1018
  },
1006
1019
  },
1007
1020
  };
1021
+
1022
+ // ═══════════════════════════════════════════════════════════════════════════
1023
+ // GitHub ↔ Kanban Sync
1024
+ // Replaces github-reconciler.mjs — reconciles PR state with kanban board.
1025
+ // ═══════════════════════════════════════════════════════════════════════════
1026
+
1027
+ resetLayout();
1028
+
1029
+ export const GITHUB_KANBAN_SYNC_TEMPLATE = {
1030
+ id: "template-github-kanban-sync",
1031
+ name: "GitHub ↔ Kanban Sync",
1032
+ description:
1033
+ "Reconciles GitHub PR state with the bosun kanban board every 5 minutes. " +
1034
+ "Marks tasks as in-review when bosun-attached PRs open, moves them to done " +
1035
+ "when PRs are merged, and posts completion comments via the kanban API. " +
1036
+ "Replaces the legacy github-reconciler.mjs module.",
1037
+ category: "github",
1038
+ enabled: true,
1039
+ recommended: true,
1040
+ trigger: "trigger.schedule",
1041
+ variables: {
1042
+ lookbackHours: 24,
1043
+ repoScope: "auto",
1044
+ },
1045
+ nodes: [
1046
+ node("trigger", "trigger.schedule", "Sync Every 5 min", {
1047
+ intervalMs: 300_000,
1048
+ cron: "*/5 * * * *",
1049
+ }, { x: 400, y: 50 }),
1050
+
1051
+ node("fetch-pr-state", "action.run_command", "Fetch Bosun PR State", {
1052
+ command: [
1053
+ "node -e \"",
1054
+ "const {execFileSync}=require('child_process');",
1055
+ "const hours=Number('{{lookbackHours}}')||24;",
1056
+ "const repoScope=String('{{repoScope}}'||'auto').trim();",
1057
+ "const since=new Date(Date.now()-hours*3600000).toISOString();",
1058
+ "function ghJson(args){",
1059
+ " try{const o=execFileSync('gh',args,{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim();return o?JSON.parse(o):[];}",
1060
+ " catch{return [];}",
1061
+ "}",
1062
+ "const merged=ghJson(['pr','list','--state','merged','--label','bosun-attached','--json','number,title,body,headRefName,mergedAt','--limit','50']);",
1063
+ "const open=ghJson(['pr','list','--state','open','--label','bosun-attached','--json','number,title,body,headRefName,isDraft','--limit','50']);",
1064
+ "function extractTaskId(pr){",
1065
+ " const src=String((pr.body||'')+'\\n'+(pr.title||''));",
1066
+ " const m=src.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i);",
1067
+ " return m?m[1].trim():null;",
1068
+ "}",
1069
+ "const recentMerged=merged.filter(p=>!p.mergedAt||new Date(p.mergedAt)>=new Date(since));",
1070
+ "console.log(JSON.stringify({",
1071
+ " repoScope,",
1072
+ " merged:recentMerged.map(p=>({n:p.number,title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})),",
1073
+ " open:open.filter(p=>!p.isDraft).map(p=>({n:p.number,title:p.title,branch:p.headRefName,taskId:extractTaskId(p)})),",
1074
+ "}));",
1075
+ "\"",
1076
+ ].join(" "),
1077
+ continueOnError: true,
1078
+ }, { x: 400, y: 200 }),
1079
+
1080
+ node("has-updates", "condition.expression", "Any Updates?", {
1081
+ expression:
1082
+ "(()=>{try{" +
1083
+ "const o=$ctx.getNodeOutput('fetch-pr-state')?.output;" +
1084
+ "const d=JSON.parse(o||'{}');" +
1085
+ "return (d.merged||[]).length>0||(d.open||[]).length>0;" +
1086
+ "}catch{return false;}})()",
1087
+ }, { x: 400, y: 370 }),
1088
+
1089
+ node("sync-agent", "action.run_agent", "Sync PR State → Kanban", {
1090
+ prompt:
1091
+ "You are the Bosun GitHub-Kanban sync agent. Sync the kanban board " +
1092
+ "to match the GitHub PR state shown below.\n\n" +
1093
+ "PR state (JSON from fetch-pr-state node output):\n" +
1094
+ "{{$ctx.getNodeOutput('fetch-pr-state')?.output}}\n\n" +
1095
+ "RULES:\n" +
1096
+ "1. For each MERGED PR entry with a taskId: update the kanban task to done.\n" +
1097
+ " Use the available bosun/vk CLI, for example:\n" +
1098
+ " node task-cli.mjs update <taskId> --status done\n" +
1099
+ " Or check available commands: ls *.mjs | grep -i task\n" +
1100
+ "2. For each OPEN (non-draft) PR entry with a taskId: if the task is not\n" +
1101
+ " already in inreview or done status, update it to inreview.\n" +
1102
+ "3. Only act on entries that have a non-null taskId.\n" +
1103
+ "4. Log each update and whether it succeeded.\n" +
1104
+ "5. Do NOT close, merge, or modify any PR.\n" +
1105
+ "6. Do NOT create new tasks — only update existing ones.",
1106
+ sdk: "auto",
1107
+ timeoutMs: 300_000,
1108
+ continueOnError: true,
1109
+ }, { x: 400, y: 530 }),
1110
+
1111
+ node("done", "notify.log", "Sync Complete", {
1112
+ message: "GitHub ↔ Kanban sync cycle complete",
1113
+ level: "info",
1114
+ }, { x: 400, y: 700 }),
1115
+
1116
+ node("skip", "notify.log", "No PR Updates", {
1117
+ message: "No bosun PR changes to sync this cycle",
1118
+ level: "debug",
1119
+ }, { x: 650, y: 450 }),
1120
+ ],
1121
+ edges: [
1122
+ edge("trigger", "fetch-pr-state"),
1123
+ edge("fetch-pr-state", "has-updates"),
1124
+ edge("has-updates", "sync-agent", { condition: "$output?.result === true" }),
1125
+ edge("has-updates", "skip", { condition: "$output?.result !== true" }),
1126
+ edge("sync-agent", "done"),
1127
+ ],
1128
+ metadata: {
1129
+ author: "bosun",
1130
+ version: 1,
1131
+ createdAt: "2025-07-10T00:00:00Z",
1132
+ templateVersion: "1.0.0",
1133
+ tags: ["github", "kanban", "sync", "reconcile", "pr", "automation"],
1134
+ replaces: {
1135
+ module: "github-reconciler.mjs",
1136
+ functions: [
1137
+ "startGitHubReconciler",
1138
+ "stopGitHubReconciler",
1139
+ "GitHubReconciler (setInReview, syncMergedPRs, reconcileTaskStatuses)",
1140
+ ],
1141
+ calledFrom: ["monitor.mjs:restartGitHubReconciler"],
1142
+ description:
1143
+ "Replaces the legacy github-reconciler.mjs module that polled GitHub PRs " +
1144
+ "and updated kanban task statuses (inreview/done) every N minutes. " +
1145
+ "This template runs the same reconciliation as an auditable, configurable " +
1146
+ "workflow with an agent-driven sync step.",
1147
+ },
1148
+ },
1149
+ };
@@ -44,6 +44,7 @@ import {
44
44
  STALE_PR_REAPER_TEMPLATE,
45
45
  RELEASE_DRAFTER_TEMPLATE,
46
46
  BOSUN_PR_WATCHDOG_TEMPLATE,
47
+ GITHUB_KANBAN_SYNC_TEMPLATE,
47
48
  } from "./workflow-templates/github.mjs";
48
49
 
49
50
  // Agents
@@ -99,6 +100,7 @@ export {
99
100
  STALE_PR_REAPER_TEMPLATE,
100
101
  RELEASE_DRAFTER_TEMPLATE,
101
102
  BOSUN_PR_WATCHDOG_TEMPLATE,
103
+ GITHUB_KANBAN_SYNC_TEMPLATE,
102
104
  FRONTEND_AGENT_TEMPLATE,
103
105
  REVIEW_AGENT_TEMPLATE,
104
106
  CUSTOM_AGENT_TEMPLATE,
@@ -148,6 +150,7 @@ export const WORKFLOW_TEMPLATES = Object.freeze([
148
150
  STALE_PR_REAPER_TEMPLATE,
149
151
  RELEASE_DRAFTER_TEMPLATE,
150
152
  BOSUN_PR_WATCHDOG_TEMPLATE,
153
+ GITHUB_KANBAN_SYNC_TEMPLATE,
151
154
  // ── Agents ──
152
155
  REVIEW_AGENT_TEMPLATE,
153
156
  FRONTEND_AGENT_TEMPLATE,