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.
Files changed (98) hide show
  1. package/.env.example +98 -16
  2. package/README.md +27 -0
  3. package/agent-event-bus.mjs +5 -5
  4. package/agent-pool.mjs +129 -12
  5. package/agent-prompts.mjs +7 -1
  6. package/agent-sdk.mjs +13 -2
  7. package/agent-supervisor.mjs +2 -2
  8. package/agent-work-report.mjs +1 -1
  9. package/anomaly-detector.mjs +6 -6
  10. package/autofix.mjs +15 -15
  11. package/bosun-skills.mjs +4 -4
  12. package/bosun.schema.json +160 -4
  13. package/claude-shell.mjs +11 -11
  14. package/cli.mjs +21 -21
  15. package/codex-config.mjs +19 -19
  16. package/codex-shell.mjs +180 -29
  17. package/config-doctor.mjs +27 -2
  18. package/config.mjs +60 -7
  19. package/copilot-shell.mjs +4 -4
  20. package/error-detector.mjs +1 -1
  21. package/fleet-coordinator.mjs +2 -2
  22. package/gemini-shell.mjs +692 -0
  23. package/github-oauth-portal.mjs +1 -1
  24. package/github-reconciler.mjs +2 -2
  25. package/kanban-adapter.mjs +741 -168
  26. package/merge-strategy.mjs +25 -25
  27. package/monitor.mjs +123 -105
  28. package/opencode-shell.mjs +22 -22
  29. package/package.json +7 -1
  30. package/postinstall.mjs +22 -22
  31. package/pr-cleanup-daemon.mjs +6 -6
  32. package/prepublish-check.mjs +4 -4
  33. package/presence.mjs +2 -2
  34. package/primary-agent.mjs +85 -7
  35. package/publish.mjs +1 -1
  36. package/review-agent.mjs +1 -1
  37. package/session-tracker.mjs +11 -0
  38. package/setup-web-server.mjs +429 -21
  39. package/setup.mjs +367 -12
  40. package/shared-knowledge.mjs +1 -1
  41. package/startup-service.mjs +9 -9
  42. package/stream-resilience.mjs +58 -4
  43. package/sync-engine.mjs +2 -2
  44. package/task-assessment.mjs +9 -9
  45. package/task-cli.mjs +1 -1
  46. package/task-complexity.mjs +71 -2
  47. package/task-context.mjs +1 -2
  48. package/task-executor.mjs +104 -41
  49. package/telegram-bot.mjs +825 -494
  50. package/telegram-sentinel.mjs +28 -28
  51. package/ui/app.js +256 -23
  52. package/ui/app.monolith.js +1 -1
  53. package/ui/components/agent-selector.js +4 -3
  54. package/ui/components/chat-view.js +101 -28
  55. package/ui/components/diff-viewer.js +3 -3
  56. package/ui/components/kanban-board.js +3 -3
  57. package/ui/components/session-list.js +255 -35
  58. package/ui/components/workspace-switcher.js +3 -3
  59. package/ui/demo.html +209 -194
  60. package/ui/index.html +3 -3
  61. package/ui/modules/icon-utils.js +206 -142
  62. package/ui/modules/icons.js +2 -27
  63. package/ui/modules/settings-schema.js +29 -5
  64. package/ui/modules/streaming.js +30 -2
  65. package/ui/modules/vision-stream.js +275 -0
  66. package/ui/modules/voice-client.js +102 -9
  67. package/ui/modules/voice-fallback.js +62 -6
  68. package/ui/modules/voice-overlay.js +594 -59
  69. package/ui/modules/voice.js +31 -38
  70. package/ui/setup.html +284 -34
  71. package/ui/styles/components.css +47 -0
  72. package/ui/styles/sessions.css +75 -0
  73. package/ui/tabs/agents.js +73 -43
  74. package/ui/tabs/chat.js +37 -40
  75. package/ui/tabs/control.js +2 -2
  76. package/ui/tabs/dashboard.js +1 -1
  77. package/ui/tabs/infra.js +10 -10
  78. package/ui/tabs/library.js +8 -8
  79. package/ui/tabs/logs.js +10 -10
  80. package/ui/tabs/settings.js +20 -20
  81. package/ui/tabs/tasks.js +76 -47
  82. package/ui-server.mjs +1761 -124
  83. package/update-check.mjs +13 -13
  84. package/ve-kanban.mjs +1 -1
  85. package/whatsapp-channel.mjs +5 -5
  86. package/workflow-engine.mjs +20 -1
  87. package/workflow-nodes.mjs +904 -4
  88. package/workflow-templates/agents.mjs +321 -7
  89. package/workflow-templates/ci-cd.mjs +6 -6
  90. package/workflow-templates/github.mjs +156 -84
  91. package/workflow-templates/planning.mjs +8 -8
  92. package/workflow-templates/reliability.mjs +8 -8
  93. package/workflow-templates/security.mjs +3 -3
  94. package/workflow-templates.mjs +15 -9
  95. package/workspace-manager.mjs +85 -1
  96. package/workspace-monitor.mjs +2 -2
  97. package/workspace-registry.mjs +2 -2
  98. package/worktree-manager.mjs +1 -1
@@ -132,7 +132,7 @@ Respond with JSON: { "action": "<choice>", "reason": "<why>", "message": "<optio
132
132
  }, { x: 700, y: 680 }),
133
133
 
134
134
  node("do-escalate", "notify.telegram", "Escalate to Human", {
135
- message: "👀 PR #{{prNumber}} needs manual review: {{decision.reason}}",
135
+ message: ":eye: PR #{{prNumber}} needs manual review: {{decision.reason}}",
136
136
  }, { x: 900, y: 680 }),
137
137
 
138
138
  node("action-succeeded", "condition.expression", "Action Succeeded?", {
@@ -142,7 +142,7 @@ Respond with JSON: { "action": "<choice>", "reason": "<why>", "message": "<optio
142
142
 
143
143
  node("notify-action-failed", "notify.telegram", "Escalate Action Failure", {
144
144
  message:
145
- "⚠️ PR #{{prNumber}} workflow action failed after retries ({{decision.action}}). " +
145
+ ":alert: PR #{{prNumber}} workflow action failed after retries ({{decision.action}}). " +
146
146
  "Reason: {{decision.reason}}. Manual follow-up required.",
147
147
  }, { x: 760, y: 850 }),
148
148
 
@@ -300,7 +300,7 @@ export const PR_CONFLICT_RESOLVER_TEMPLATE = {
300
300
  id: "template-pr-conflict-resolver",
301
301
  name: "PR Conflict Resolver",
302
302
  description:
303
- "⚠️ SUPERSEDED for bosun-managed repos — use the Bosun PR Watchdog " +
303
+ ":alert: SUPERSEDED for bosun-managed repos — use the Bosun PR Watchdog " +
304
304
  "(template-bosun-pr-watchdog) instead. The Watchdog consolidates conflict " +
305
305
  "resolution, CI-failure repair, diff-safety review, and merge into one " +
306
306
  "cycle with a single gh API call and a mandatory review gate before any merge. " +
@@ -413,7 +413,7 @@ export const PR_CONFLICT_RESOLVER_TEMPLATE = {
413
413
  }, { x: 200, y: 800 }),
414
414
 
415
415
  node("notify-fixed", "notify.telegram", "Notify Resolved", {
416
- message: "🔧 PR #{{targetPrNumber}} conflict resolved — awaiting CI and Watchdog review before merge",
416
+ message: ":settings: PR #{{targetPrNumber}} conflict resolved — awaiting CI and Watchdog review before merge",
417
417
  silent: true,
418
418
  }, { x: 200, y: 960 }),
419
419
 
@@ -507,7 +507,7 @@ export const STALE_PR_REAPER_TEMPLATE = {
507
507
  }, { x: 200, y: 800 }),
508
508
 
509
509
  node("summary", "notify.telegram", "Summary", {
510
- message: "🧹 Stale PR cleanup complete",
510
+ message: ":trash: Stale PR cleanup complete",
511
511
  silent: true,
512
512
  }, { x: 200, y: 950 }),
513
513
 
@@ -596,19 +596,19 @@ Generate professional release notes in the following format:
596
596
 
597
597
  # What's Changed
598
598
 
599
- ## 🚀 Features
599
+ ## :rocket: Features
600
600
  - [list feat: commits with PR references]
601
601
 
602
- ## 🐛 Bug Fixes
602
+ ## :bug: Bug Fixes
603
603
  - [list fix: commits with PR references]
604
604
 
605
- ## 🔧 Improvements
605
+ ## :settings: Improvements
606
606
  - [list refactor/perf/style commits]
607
607
 
608
- ## 📚 Documentation
608
+ ## :u1f4da: Documentation
609
609
  - [list docs: commits]
610
610
 
611
- ## 🏗️ Internal
611
+ ## :hammer: Internal
612
612
  - [list chore/ci/build commits]
613
613
 
614
614
  Omit empty sections. Include contributor attribution. Be concise.`,
@@ -669,8 +669,8 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
669
669
  id: "template-bosun-pr-watchdog",
670
670
  name: "Bosun PR Watchdog",
671
671
  description:
672
- "Scans open bosun-attached PRs on a schedule. Makes ONE gh API call to " +
673
- "fetch and classify all PRs, then: labels conflicting or failing-CI PRs " +
672
+ "Scans open bosun-attached PRs on a schedule. Makes one gh pr list call " +
673
+ "per target repo to fetch and classify PRs, then: labels conflicting or failing-CI PRs " +
674
674
  "with bosun-needs-fix and dispatches a repair agent; sends merge candidates " +
675
675
  "through a MANDATORY agent review gate that checks diff stats before any " +
676
676
  "merge — preventing destructive PRs (e.g. -183k lines) from being silently " +
@@ -682,6 +682,9 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
682
682
  mergeMethod: "squash", // squash | merge | rebase
683
683
  labelNeedsFix: "bosun-needs-fix", // applied to CI failures and conflicts
684
684
  labelNeedsReview: "bosun-needs-human-review", // applied when review agent flags a suspicious diff
685
+ // auto: active workspace repos from bosun.config.json (fallback current repo)
686
+ // all/current/<owner/repo>/comma,list also supported.
687
+ repoScope: "auto",
685
688
  maxPrs: 25,
686
689
  intervalMs: 300_000, // 5 minutes
687
690
  // Merge-safety thresholds checked by the review agent:
@@ -696,73 +699,141 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
696
699
  }, { x: 400, y: 50 }),
697
700
 
698
701
  // ─────────────────────────────────────────────────────────────────────────
699
- // STEP 1: ONE gh API call fetch all fields we need in a single request.
700
- // classify-and-label does all subsequent classification + labeling inline
701
- // from this one response, so no repeated gh pr list calls later.
702
+ // STEP 1: One gh pr list per target repo, then classify+label in-memory.
703
+ // This avoids duplicate fetches and keeps per-cycle gh traffic bounded.
702
704
  // ─────────────────────────────────────────────────────────────────────────
703
705
  node("fetch-and-classify", "action.run_command", "Fetch, Classify & Label PRs", {
704
706
  // Fetches all open bosun-attached PRs with every field needed for
705
- // classification. Pipes into an inline Node script that:
707
+ // classification. Runs one list call per target repo (auto-discovered
708
+ // from bosun.config.json workspaces by default), then:
706
709
  // • Classifies each PR into: ready | conflict | ci_failure | pending | draft
707
710
  // • Labels conflict/ci_failure PRs with bosun-needs-fix (skips if already present)
708
711
  // • Outputs a JSON summary used by all downstream nodes/agents
709
- // Total gh API calls this node makes: 1 list + N edits (only for newly-broken PRs)
712
+ // Total gh API calls this node makes: R list calls + N edits
713
+ // (R = target repos, N = newly-broken PRs needing fix label).
710
714
  command: [
711
- "gh pr list --label bosun-attached --state open",
712
- "--json number,title,headRefName,baseRefName,isDraft,mergeable,statusCheckRollup,labels,url",
713
- "--limit {{maxPrs}}",
714
- "| node -e \"",
715
+ "node -e \"",
716
+ "const fs=require('fs');",
717
+ "const path=require('path');",
718
+ "const {execFileSync}=require('child_process');",
715
719
  "const LABEL_FIX='{{labelNeedsFix}}';",
716
- "const {execSync}=require('child_process');",
717
- "let raw='';",
718
- "process.stdin.on('data',c=>raw+=c);",
719
- "process.stdin.on('end',()=>{",
720
- " let prs=[];",
721
- " try{prs=JSON.parse(raw);}catch(e){console.log(JSON.stringify({error:e.message,total:0}));return;}",
722
- " const FAIL_STATES=new Set(['FAILURE','ERROR','TIMED_OUT','CANCELLED','STARTUP_FAILURE']);",
723
- " const PEND_STATES=new Set(['PENDING','IN_PROGRESS','QUEUED','WAITING','REQUESTED','EXPECTED']);",
724
- " const CONFLICT_MERGEABLES=new Set(['CONFLICTING','BEHIND','DIRTY']);",
725
- " const readyCandidates=[],conflicts=[],ciFailures=[],pending=[],drafted=[];",
726
- " let newlyLabeled=0;",
727
- " for(const pr of prs){",
728
- " const labels=(pr.labels||[]).map(l=>l.name);",
729
- " const hasFixLabel=labels.includes(LABEL_FIX);",
730
- " const checks=pr.statusCheckRollup||[];",
731
- " const hasFail=checks.some(c=>FAIL_STATES.has(c.conclusion||c.state||''));",
732
- " const hasPend=checks.some(c=>PEND_STATES.has(c.conclusion||c.state||''));",
733
- " const isConflict=CONFLICT_MERGEABLES.has(String(pr.mergeable||'').toUpperCase());",
734
- " const isDraft=pr.isDraft===true;",
735
- " if(isDraft){drafted.push(pr.number);continue;}",
736
- " if(isConflict){",
737
- " conflicts.push({n:pr.number,branch:pr.headRefName,base:pr.baseRefName,url:pr.url});",
738
- " if(!hasFixLabel){",
739
- " try{execSync('gh pr edit '+pr.number+' --add-label '+LABEL_FIX,{stdio:'pipe'});newlyLabeled++;}",
740
- " catch(e){process.stderr.write('label err #'+pr.number+': '+e.message+'\\n');}",
720
+ "const MAX_PRS=Math.max(1,Number('{{maxPrs}}')||25);",
721
+ "const REPO_SCOPE=String('{{repoScope}}'||'auto').trim();",
722
+ "const FIELDS='number,title,headRefName,baseRefName,isDraft,mergeable,statusCheckRollup,labels,url';",
723
+ "const FAIL_STATES=new Set(['FAILURE','ERROR','TIMED_OUT','CANCELLED','STARTUP_FAILURE']);",
724
+ "const PEND_STATES=new Set(['PENDING','IN_PROGRESS','QUEUED','WAITING','REQUESTED','EXPECTED']);",
725
+ "const CONFLICT_MERGEABLES=new Set(['CONFLICTING','BEHIND','DIRTY']);",
726
+ "function ghJson(args){const out=execFileSync('gh',args,{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim();return out?JSON.parse(out):[];}",
727
+ "function configPath(){",
728
+ " const home=String(process.env.BOSUN_HOME||process.env.VK_PROJECT_DIR||'').trim();",
729
+ " return home?path.join(home,'bosun.config.json'):path.join(process.cwd(),'bosun.config.json');",
730
+ "}",
731
+ "function collectReposFromConfig(){",
732
+ " const repos=[];",
733
+ " try{",
734
+ " const cfg=JSON.parse(fs.readFileSync(configPath(),'utf8'));",
735
+ " const workspaces=Array.isArray(cfg?.workspaces)?cfg.workspaces:[];",
736
+ " if(workspaces.length>0){",
737
+ " const active=String(cfg?.activeWorkspace||'').trim().toLowerCase();",
738
+ " const activeWs=active?workspaces.find(w=>String(w?.id||'').trim().toLowerCase()===active):null;",
739
+ " const wsList=activeWs?[activeWs]:workspaces;",
740
+ " for(const ws of wsList){",
741
+ " for(const repo of (Array.isArray(ws?.repos)?ws.repos:[])){",
742
+ " const slug=typeof repo==='string'?String(repo).trim():String(repo?.slug||'').trim();",
743
+ " if(slug) repos.push(slug);",
744
+ " }",
741
745
  " }",
742
- " } else if(hasFail){",
743
- " ciFailures.push({n:pr.number,branch:pr.headRefName,url:pr.url});",
744
- " if(!hasFixLabel){",
745
- " try{execSync('gh pr edit '+pr.number+' --add-label '+LABEL_FIX,{stdio:'pipe'});newlyLabeled++;}",
746
- " catch(e){process.stderr.write('label err #'+pr.number+': '+e.message+'\\n');}",
746
+ " }",
747
+ " if(repos.length===0){",
748
+ " for(const repo of (Array.isArray(cfg?.repos)?cfg.repos:[])){",
749
+ " const slug=typeof repo==='string'?String(repo).trim():String(repo?.slug||'').trim();",
750
+ " if(slug) repos.push(slug);",
747
751
  " }",
748
- " } else if(hasPend){",
749
- " pending.push(pr.number);",
750
- " } else if(checks.length>0&&!hasFixLabel){",
751
- " /* CI all-passing, no conflicts, not draft — a review candidate */",
752
- " readyCandidates.push({n:pr.number,branch:pr.headRefName,base:pr.baseRefName,url:pr.url,title:pr.title});",
753
752
  " }",
753
+ " }catch{}",
754
+ " return repos;",
755
+ "}",
756
+ "function resolveRepoTargets(){",
757
+ " if(REPO_SCOPE&&REPO_SCOPE!=='auto'&&REPO_SCOPE!=='all'&&REPO_SCOPE!=='current'){",
758
+ " return [...new Set(REPO_SCOPE.split(',').map(v=>v.trim()).filter(Boolean))];",
759
+ " }",
760
+ " if(REPO_SCOPE==='current') return [''];",
761
+ " const fromConfig=collectReposFromConfig();",
762
+ " if(fromConfig.length>0) return [...new Set(fromConfig)];",
763
+ " const envRepo=String(process.env.GITHUB_REPOSITORY||'').trim();",
764
+ " if(envRepo) return [envRepo];",
765
+ " return [''];",
766
+ "}",
767
+ "function parseRepoFromUrl(url){",
768
+ " const raw=String(url||'');",
769
+ " const marker='github.com/';",
770
+ " const idx=raw.toLowerCase().indexOf(marker);",
771
+ " if(idx<0) return '';",
772
+ " const tail=raw.slice(idx+marker.length).split('/');",
773
+ " if(tail.length<2) return '';",
774
+ " const owner=String(tail[0]||'').trim();",
775
+ " const repo=String(tail[1]||'').trim();",
776
+ " return owner&&repo?(owner+'/'+repo):'';",
777
+ "}",
778
+ "const repoTargets=resolveRepoTargets();",
779
+ "const prs=[];",
780
+ "const repoErrors=[];",
781
+ "for(const target of repoTargets){",
782
+ " const repo=String(target||'').trim();",
783
+ " const args=['pr','list','--label','bosun-attached','--state','open','--json',FIELDS,'--limit',String(MAX_PRS)];",
784
+ " if(repo) args.push('--repo',repo);",
785
+ " try{",
786
+ " const list=ghJson(args);",
787
+ " for(const pr of (Array.isArray(list)?list:[])){",
788
+ " const prRepo=repo||parseRepoFromUrl(pr?.url)||String(process.env.GITHUB_REPOSITORY||'').trim();",
789
+ " prs.push({...pr,__repo:prRepo});",
790
+ " }",
791
+ " }catch(e){",
792
+ " repoErrors.push({repo:repo||'current',error:String(e?.message||e)});",
793
+ " }",
794
+ "}",
795
+ "const readyCandidates=[],conflicts=[],ciFailures=[],pending=[],drafted=[];",
796
+ "let newlyLabeled=0;",
797
+ "for(const pr of prs){",
798
+ " const labels=(pr.labels||[]).map(l=>typeof l==='string'?l:l?.name).filter(Boolean);",
799
+ " const hasFixLabel=labels.includes(LABEL_FIX);",
800
+ " const checks=pr.statusCheckRollup||[];",
801
+ " const hasFail=checks.some(c=>FAIL_STATES.has(c.conclusion||c.state||''));",
802
+ " const hasPend=checks.some(c=>PEND_STATES.has(c.conclusion||c.state||''));",
803
+ " const isConflict=CONFLICT_MERGEABLES.has(String(pr.mergeable||'').toUpperCase());",
804
+ " const isDraft=pr.isDraft===true;",
805
+ " const repo=String(pr.__repo||'').trim();",
806
+ " if(isDraft){drafted.push({n:pr.number,repo});continue;}",
807
+ " if(isConflict){",
808
+ " conflicts.push({n:pr.number,repo,branch:pr.headRefName,base:pr.baseRefName,url:pr.url});",
809
+ " if(!hasFixLabel){",
810
+ " try{const editArgs=['pr','edit',String(pr.number),'--add-label',LABEL_FIX];if(repo)editArgs.push('--repo',repo);execFileSync('gh',editArgs,{encoding:'utf8',stdio:['pipe','pipe','pipe']});newlyLabeled++;}",
811
+ " catch(e){process.stderr.write('label err '+(repo?repo+' ':'')+'#'+pr.number+': '+(e?.message||e)+'\\\\n');}",
812
+ " }",
813
+ " } else if(hasFail){",
814
+ " ciFailures.push({n:pr.number,repo,branch:pr.headRefName,url:pr.url});",
815
+ " if(!hasFixLabel){",
816
+ " try{const editArgs=['pr','edit',String(pr.number),'--add-label',LABEL_FIX];if(repo)editArgs.push('--repo',repo);execFileSync('gh',editArgs,{encoding:'utf8',stdio:['pipe','pipe','pipe']});newlyLabeled++;}",
817
+ " catch(e){process.stderr.write('label err '+(repo?repo+' ':'')+'#'+pr.number+': '+(e?.message||e)+'\\\\n');}",
818
+ " }",
819
+ " } else if(hasPend){",
820
+ " pending.push({n:pr.number,repo});",
821
+ " } else if(checks.length>0&&!hasFixLabel){",
822
+ " readyCandidates.push({n:pr.number,repo,branch:pr.headRefName,base:pr.baseRefName,url:pr.url,title:pr.title});",
754
823
  " }",
755
- " console.log(JSON.stringify({",
756
- " total:prs.length,",
757
- " readyCandidates,",
758
- " conflicts,",
759
- " ciFailures,",
760
- " pending:pending.length,",
761
- " drafted:drafted.length,",
762
- " newlyLabeled,",
763
- " fixNeeded:conflicts.length+ciFailures.length",
764
- " }));",
765
- "});",
824
+ "}",
825
+ "console.log(JSON.stringify({",
826
+ " total:prs.length,",
827
+ " reposScanned:repoTargets.length,",
828
+ " repoErrors,",
829
+ " readyCandidates,",
830
+ " conflicts,",
831
+ " ciFailures,",
832
+ " pending:pending.length,",
833
+ " drafted:drafted.length,",
834
+ " newlyLabeled,",
835
+ " fixNeeded:conflicts.length+ciFailures.length",
836
+ "}));",
766
837
  "\"",
767
838
  ].join(" "),
768
839
  continueOnError: false,
@@ -795,8 +866,9 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
795
866
  prompt:
796
867
  "You are a Bosun PR repair agent. A watchdog workflow has identified " +
797
868
  "bosun-attached PRs that need fixing.\n\n" +
798
- "Run this single command to get the current list of PRs needing work:\n" +
799
- " gh pr list --label bosun-needs-fix --label bosun-attached --state open \\\n" +
869
+ "For EACH target repo, list PRs needing work. Always include --repo <owner/repo>.\n" +
870
+ "Example:\n" +
871
+ " gh pr list --repo <owner/repo> --label bosun-needs-fix --label bosun-attached --state open \\\n" +
800
872
  " --json number,title,headRefName,baseRefName,mergeable,statusCheckRollup,labels,url \\\n" +
801
873
  " --limit 10\n\n" +
802
874
  "For each PR returned, follow the appropriate path:\n\n" +
@@ -809,13 +881,13 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
809
881
  "6. git push --force-with-lease origin <headRefName>\n\n" +
810
882
  "CI FAILURE (statusCheckRollup has FAILURE/ERROR/TIMED_OUT entries):\n" +
811
883
  "1. git checkout <headRefName>\n" +
812
- "2. Inspect CI logs: gh run list --branch <headRefName> --limit 3\n" +
813
- " Then: gh run view <run-id> --log-failed\n" +
884
+ "2. Inspect CI logs: gh run list --repo <owner/repo> --branch <headRefName> --limit 3\n" +
885
+ " Then: gh run view <run-id> --repo <owner/repo> --log-failed\n" +
814
886
  "3. Fix the root cause (lint, type error, failing test, build error).\n" +
815
887
  "4. Commit: fix(<scope>): <description> (conventional commit format)\n" +
816
888
  "5. Push the branch — CI will re-trigger automatically.\n\n" +
817
889
  "AFTER fixing either type:\n" +
818
- "- Remove the bosun-needs-fix label: gh pr edit <number> --remove-label bosun-needs-fix\n\n" +
890
+ "- Remove the bosun-needs-fix label: gh pr edit <number> --repo <owner/repo> --remove-label bosun-needs-fix\n\n" +
819
891
  "STRICT RULES:\n" +
820
892
  "- Fix only what breaks CI or causes the conflict. No scope creep.\n" +
821
893
  "- Do NOT merge, close, or approve any PR.\n" +
@@ -847,33 +919,33 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
847
919
  "any PR is merged. Your job is to inspect each merge candidate and decide " +
848
920
  "whether it is safe to merge.\n\n" +
849
921
  "MERGE CANDIDATES (CI passing, no conflicts, bosun-attached):\n" +
850
- " Run: gh pr list --label bosun-attached --state open \\\n" +
922
+ " Run per target repo: gh pr list --repo <owner/repo> --label bosun-attached --state open \\\n" +
851
923
  " --json number,title,headRefName,isDraft,statusCheckRollup,labels,url \\\n" +
852
924
  " --limit {{maxPrs}}\n" +
853
925
  " Filter to PRs where: not isDraft, CI all-passing, no bosun-needs-fix label.\n\n" +
854
926
  "FOR EACH CANDIDATE — before merging, run:\n" +
855
- " gh pr view <number> --json number,title,additions,deletions,changedFiles,body,baseRefName\n\n" +
927
+ " gh pr view <number> --repo <owner/repo> --json number,title,additions,deletions,changedFiles,body,baseRefName\n\n" +
856
928
  "SAFETY CHECKS (ALL must pass before merging):\n\n" +
857
929
  "1. DESTRUCTIVE DIFF CHECK:\n" +
858
930
  " If (deletions > additions × {{suspiciousDeletionRatio}}) AND (deletions > {{minDestructiveDeletions}}):\n" +
859
931
  " → This PR deletes far more than it adds — HOLD IT.\n" +
860
- " → Run: gh pr edit <number> --add-label {{labelNeedsReview}}\n" +
861
- " → Run: gh pr comment <number> --body '⚠️ **Bosun Review Agent: merge held** — " +
932
+ " → Run: gh pr edit <number> --repo <owner/repo> --add-label {{labelNeedsReview}}\n" +
933
+ " → Run: gh pr comment <number> --repo <owner/repo> --body ':alert: **Bosun Review Agent: merge held** — " +
862
934
  "This PR deletes significantly more lines than it adds (deletions: <X>, additions: <Y>). " +
863
935
  "A human should verify this is intentional before merging.'\n" +
864
936
  " → Do NOT merge this PR. Move to next candidate.\n\n" +
865
937
  "2. DIFF SANITY CHECK (for PRs that pass the ratio check):\n" +
866
- " Run: gh pr diff <number> | head -200\n" +
938
+ " Run: gh pr diff <number> --repo <owner/repo> | head -200\n" +
867
939
  " Look for: mass file deletions, removal of entire modules/directories, " +
868
940
  " files changed that are unrelated to the PR description.\n" +
869
941
  " If something looks wrong → HOLD with bosun-needs-human-review label + comment.\n\n" +
870
942
  "3. CI STATUS RECONFIRM:\n" +
871
- " Run: gh pr checks <number> --json name,state,conclusion\n" +
943
+ " Run: gh pr checks <number> --repo <owner/repo> --json name,state,conclusion\n" +
872
944
  " Ensure ALL checks have conclusion SUCCESS/SKIPPED/NEUTRAL. " +
873
945
  " If any are pending or failing → do NOT merge (CI may still be running).\n\n" +
874
946
  "MERGE (only if ALL checks pass):\n" +
875
- " gh pr merge <number> --{{mergeMethod}} --delete-branch\n" +
876
- " Log: Merged PR #<number> — <title>\n\n" +
947
+ " gh pr merge <number> --repo <owner/repo> --{{mergeMethod}} --delete-branch\n" +
948
+ " Log: :check: Merged PR #<number> — <title>\n\n" +
877
949
  "STRICT RULES:\n" +
878
950
  "- NEVER merge if ANY safety check fails. When in doubt, HOLD.\n" +
879
951
  "- NEVER merge PRs without the bosun-attached label.\n" +
@@ -889,7 +961,7 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
889
961
 
890
962
  node("notify", "notify.telegram", "Watchdog Report", {
891
963
  message:
892
- "🐕 Bosun PR Watchdog cycle complete — " +
964
+ ":bug: Bosun PR Watchdog cycle complete — " +
893
965
  "fix-dispatched: {{fixNeeded}} | candidates-reviewed: {{readyCandidates}}",
894
966
  silent: true,
895
967
  }, { x: 400, y: 900 }),
@@ -915,16 +987,16 @@ export const BOSUN_PR_WATCHDOG_TEMPLATE = {
915
987
  ],
916
988
  metadata: {
917
989
  author: "bosun",
918
- version: 2,
990
+ version: 3,
919
991
  createdAt: "2025-07-01T00:00:00Z",
920
- templateVersion: "2.0.0",
992
+ templateVersion: "2.1.0",
921
993
  tags: ["github", "pr", "ci", "merge", "watchdog", "bosun-attached", "safety"],
922
994
  replaces: {
923
995
  module: "agent-hooks.mjs",
924
996
  functions: ["registerBuiltinHooks (PostPR block)"],
925
997
  calledFrom: [],
926
998
  description:
927
- "v2: Consolidates all gh API calls into ONE gh pr list fetch per cycle. " +
999
+ "v2: Consolidates PR polling into one gh pr list fetch per target repo per cycle. " +
928
1000
  "Adds mandatory review gate agent that checks diff stats (additions/deletions " +
929
1001
  "ratio) and diff content before any merge — preventing destructive PRs from " +
930
1002
  "being auto-merged. Adds conflict detection via the 'mergeable' field. " +
@@ -77,7 +77,7 @@ export const TASK_PLANNER_TEMPLATE = {
77
77
  }, { x: 200, y: 830 }),
78
78
 
79
79
  node("notify-done", "notify.telegram", "Notify Tasks Created", {
80
- message: "🗂️ Task planner created {{materialize-tasks.createdCount}} backlog tasks (skipped {{materialize-tasks.skippedCount}} duplicates).",
80
+ message: ":folder: Task planner created {{materialize-tasks.createdCount}} backlog tasks (skipped {{materialize-tasks.skippedCount}} duplicates).",
81
81
  silent: true,
82
82
  }, { x: 200, y: 960 }),
83
83
 
@@ -171,7 +171,7 @@ export const TASK_REPLENISH_TEMPLATE = {
171
171
  }, { x: 400, y: 570 }),
172
172
 
173
173
  node("notify", "notify.telegram", "Notify", {
174
- message: "🔄 Scheduled replenishment created {{materialize-tasks.createdCount}} tasks (skipped {{materialize-tasks.skippedCount}}).",
174
+ message: ":refresh: Scheduled replenishment created {{materialize-tasks.createdCount}} tasks (skipped {{materialize-tasks.skippedCount}}).",
175
175
  silent: true,
176
176
  }, { x: 400, y: 700 }),
177
177
 
@@ -265,7 +265,7 @@ Format as a Telegram-friendly message with emoji headers. Include:
265
265
  }, { x: 400, y: 380 }),
266
266
 
267
267
  node("send-report", "notify.telegram", "Send Report", {
268
- message: "📊 **Daily Bosun Report** ({{reportTimezone}})\n\n{{reportOutput}}",
268
+ message: ":chart: **Daily Bosun Report** ({{reportTimezone}})\n\n{{reportOutput}}",
269
269
  }, { x: 400, y: 540 }),
270
270
  ],
271
271
  edges: [
@@ -360,19 +360,19 @@ Analyze the following data from the past {{lookbackDays}} days:
360
360
 
361
361
  Generate a retrospective report with these sections:
362
362
 
363
- ### 📊 Key Metrics
363
+ ### :chart: Key Metrics
364
364
  - Tasks completed vs created
365
365
  - Average task cycle time
366
366
  - PR merge rate and average review time
367
367
  - Agent success rate
368
368
 
369
- ### What Went Well
369
+ ### :check: What Went Well
370
370
  - Highlight successful patterns and wins
371
371
 
372
- ### What Didn't Go Well
372
+ ### :close: What Didn't Go Well
373
373
  - Identify bottlenecks and recurring issues
374
374
 
375
- ### 🎯 Action Items
375
+ ### :target: Action Items
376
376
  For each improvement suggestion, output a line:
377
377
  ACTION: [title] | [description]
378
378
 
@@ -399,7 +399,7 @@ Only create tasks if {{createImprovementTasks}} is true.`,
399
399
  }, { x: 250, y: 830 }),
400
400
 
401
401
  node("send-report", "notify.telegram", "Send Retro Report", {
402
- message: "📋 **Weekly Retrospective** (past {{lookbackDays}} days)\n\n{{retroOutput}}",
402
+ message: ":clipboard: **Weekly Retrospective** (past {{lookbackDays}} days)\n\n{{retroOutput}}",
403
403
  }, { x: 400, y: 980 }),
404
404
 
405
405
  node("log-no-actions", "notify.log", "No Actions Needed", {
@@ -65,7 +65,7 @@ export const ERROR_RECOVERY_TEMPLATE = {
65
65
  }, { x: 90, y: 760 }),
66
66
 
67
67
  node("escalate", "notify.telegram", "Escalate to Human", {
68
- message: "🚨 Task **{{taskTitle}}** failed after {{maxRetries}} attempts. Manual intervention needed.\n\nLast error: {{lastError}}",
68
+ message: ":alert: Task **{{taskTitle}}** failed after {{maxRetries}} attempts. Manual intervention needed.\n\nLast error: {{lastError}}",
69
69
  }, { x: 600, y: 620 }),
70
70
  ],
71
71
  edges: [
@@ -167,7 +167,7 @@ export const ANOMALY_WATCHDOG_TEMPLATE = {
167
167
  }, { x: 400, y: 550 }),
168
168
 
169
169
  node("alert-telegram", "notify.telegram", "Alert Human", {
170
- message: "⚠️ Agent anomaly detected: **{{anomalyType}}**\nSession: {{sessionId}}\nTask: {{taskTitle}}\nIntervention: auto-applied\nThresholds: stall={{stallThresholdMs}}ms token={{maxTokenUsage}} maxErrors={{maxConsecutiveErrors}}",
170
+ message: ":alert: Agent anomaly detected: **{{anomalyType}}**\nSession: {{sessionId}}\nTask: {{taskTitle}}\nIntervention: auto-applied\nThresholds: stall={{stallThresholdMs}}ms token={{maxTokenUsage}} maxErrors={{maxConsecutiveErrors}}",
171
171
  }, { x: 400, y: 700 }),
172
172
  ],
173
173
  edges: [
@@ -334,7 +334,7 @@ export const HEALTH_CHECK_TEMPLATE = {
334
334
  }, { x: 400, y: 380 }),
335
335
 
336
336
  node("alert", "notify.telegram", "Alert Issues Found", {
337
- message: "🏥 Health check found issues — run `bosun doctor` for details",
337
+ message: ":heart: Health check found issues — run `bosun doctor` for details",
338
338
  }, { x: 200, y: 540 }),
339
339
 
340
340
  node("all-ok", "notify.telegram", "All Healthy", {
@@ -480,7 +480,7 @@ export const TASK_FINALIZATION_GUARD_TEMPLATE = {
480
480
  }, { x: 240, y: 1040 }),
481
481
 
482
482
  node("notify-fail", "notify.telegram", "Notify Finalization Failure", {
483
- message: "⚠️ Task finalization failed for **{{taskTitle}}** ({{taskId}}). Repair workflow handoff triggered.",
483
+ message: ":alert: Task finalization failed for **{{taskTitle}}** ({{taskId}}). Repair workflow handoff triggered.",
484
484
  }, { x: 540, y: 900 }),
485
485
  ],
486
486
  edges: [
@@ -621,12 +621,12 @@ export const TASK_REPAIR_WORKTREE_TEMPLATE = {
621
621
  }, { x: 560, y: 880 }),
622
622
 
623
623
  node("notify-success", "notify.telegram", "Notify Repair Success", {
624
- message: " Repair workflow recovered **{{taskTitle}}** ({{taskId}}) and moved it to inreview.",
624
+ message: ":check: Repair workflow recovered **{{taskTitle}}** ({{taskId}}) and moved it to inreview.",
625
625
  silent: true,
626
626
  }, { x: 250, y: 1160 }),
627
627
 
628
628
  node("notify-escalate", "notify.telegram", "Escalate Repair Failure", {
629
- message: "🚨 Repair workflow could not recover **{{taskTitle}}** ({{taskId}}). Manual intervention required.",
629
+ message: ":alert: Repair workflow could not recover **{{taskTitle}}** ({{taskId}}). Manual intervention required.",
630
630
  }, { x: 560, y: 1020 }),
631
631
 
632
632
  node("no-worktree", "notify.log", "Missing Worktree Context", {
@@ -827,7 +827,7 @@ Output as JSON: { "severity": "...", "category": "...", "rootCause": "...", "imp
827
827
  }, { x: 400, y: 540, outputs: ["yes", "no"] }),
828
828
 
829
829
  node("create-incident-task", "action.create_task", "Create Incident Task", {
830
- title: "🚨 Incident: {{incidentCategory}}",
830
+ title: ":alert: Incident: {{incidentCategory}}",
831
831
  description: "Auto-detected incident.\n\nEvidence: {{evidence}}\n\nClassification: {{classification}}",
832
832
  tags: ["incident", "auto-detected"],
833
833
  priority: "high",
@@ -854,7 +854,7 @@ Be conservative — prefer safe mitigations over aggressive fixes.`,
854
854
  }, { x: 250, y: 840 }),
855
855
 
856
856
  node("alert-critical", "notify.telegram", "Critical Incident Alert", {
857
- message: "🚨 **CRITICAL INCIDENT**\n\nCategory: {{incidentCategory}}\nRoot cause: {{rootCause}}\n\nAgent investigating. Immediate attention may be required.",
857
+ message: ":alert: **CRITICAL INCIDENT**\n\nCategory: {{incidentCategory}}\nRoot cause: {{rootCause}}\n\nAgent investigating. Immediate attention may be required.",
858
858
  }, { x: 150, y: 540 }),
859
859
 
860
860
  node("alert-standard", "notify.log", "Log Incident", {
@@ -87,11 +87,11 @@ Limit auto-generated fix PRs to {{maxAutoFixPRs}} in this run.`,
87
87
  }, { x: 50, y: 900 }),
88
88
 
89
89
  node("alert-critical", "notify.telegram", "Alert: Critical Vuln", {
90
- message: "🚨 **CRITICAL vulnerability** found in dependencies!\n\nRun `npm audit` for details. Manual review required.",
90
+ message: ":alert: **CRITICAL vulnerability** found in dependencies!\n\nRun `npm audit` for details. Manual review required.",
91
91
  }, { x: 350, y: 750 }),
92
92
 
93
93
  node("alert-high", "notify.telegram", "Alert: High Severity", {
94
- message: "⚠️ **High severity** dependency vulnerability detected.\n\nAuto-fix changes prepared and handed off to Bosun PR lifecycle management. Please review and merge when ready.",
94
+ message: ":alert: **High severity** dependency vulnerability detected.\n\nAuto-fix changes prepared and handed off to Bosun PR lifecycle management. Please review and merge when ready.",
95
95
  silent: true,
96
96
  }, { x: 550, y: 750 }),
97
97
 
@@ -201,7 +201,7 @@ Respond as JSON: { "findings": [{ "type": "...", "severity": "critical|high|low"
201
201
  }, { x: 200, y: 650, outputs: ["yes", "no"] }),
202
202
 
203
203
  node("alert-secret", "notify.telegram", "Alert: Secret Exposed!", {
204
- message: "🔑 **SECRET EXPOSED** in recent commits!\n\nThe secret scanner found credentials in the repository. Immediate rotation required.\n\nCheck agent output for classification details.",
204
+ message: ":lock: **SECRET EXPOSED** in recent commits!\n\nThe secret scanner found credentials in the repository. Immediate rotation required.\n\nCheck agent output for classification details.",
205
205
  }, { x: 100, y: 810 }),
206
206
 
207
207
  node("create-issue", "action.run_command", "Create Remediation Issue", {
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Templates are split into category modules for easy extension:
9
9
  * workflow-templates/github.mjs — PR Merge Strategy, Triage, Conflict Resolver, Stale Reaper, Release Drafter
10
- * workflow-templates/agents.mjs — Frontend Agent, Review Agent, Custom Agent, Session Monitor, Backend Agent
10
+ * workflow-templates/agents.mjs — Frontend Agent, Review Agent, Custom Agent, Session Monitor, Backend Agent, Meeting Orchestrator + Subworkflow Chain
11
11
  * workflow-templates/planning.mjs — Task Planner, Task Replenish, Nightly Report, Sprint Retrospective
12
12
  * workflow-templates/ci-cd.mjs — Build & Deploy, Release Pipeline, Canary Deploy
13
13
  * workflow-templates/reliability.mjs — Error Recovery, Anomaly Watchdog, Workspace Hygiene, Health Check, Task Finalization Guard, Task Repair Worktree, Incident Response
@@ -53,6 +53,8 @@ import {
53
53
  CUSTOM_AGENT_TEMPLATE,
54
54
  AGENT_SESSION_MONITOR_TEMPLATE,
55
55
  BACKEND_AGENT_TEMPLATE,
56
+ VOICE_VIDEO_PARALLEL_ROLLOUT_TEMPLATE,
57
+ MEETING_SUBWORKFLOW_CHAIN_TEMPLATE,
56
58
  } from "./workflow-templates/agents.mjs";
57
59
 
58
60
  // Planning
@@ -102,6 +104,8 @@ export {
102
104
  CUSTOM_AGENT_TEMPLATE,
103
105
  AGENT_SESSION_MONITOR_TEMPLATE,
104
106
  BACKEND_AGENT_TEMPLATE,
107
+ VOICE_VIDEO_PARALLEL_ROLLOUT_TEMPLATE,
108
+ MEETING_SUBWORKFLOW_CHAIN_TEMPLATE,
105
109
  TASK_PLANNER_TEMPLATE,
106
110
  TASK_REPLENISH_TEMPLATE,
107
111
  NIGHTLY_REPORT_TEMPLATE,
@@ -127,13 +131,13 @@ export {
127
131
 
128
132
  /** Category metadata for UI grouping. */
129
133
  export const TEMPLATE_CATEGORIES = Object.freeze({
130
- github: { label: "GitHub", icon: "🐙", order: 1 },
131
- agents: { label: "Agents", icon: "🤖", order: 2 },
132
- planning: { label: "Planning", icon: "📋", order: 3 },
133
- "ci-cd": { label: "CI / CD", icon: "🔄", order: 4 },
134
- reliability: { label: "Reliability", icon: "🛡️", order: 5 },
135
- security: { label: "Security", icon: "🔒", order: 6 },
136
- custom: { label: "Custom", icon: "⚙️", order: 7 },
134
+ github: { label: "GitHub", icon: ":git:", order: 1 },
135
+ agents: { label: "Agents", icon: ":bot:", order: 2 },
136
+ planning: { label: "Planning", icon: ":clipboard:", order: 3 },
137
+ "ci-cd": { label: "CI / CD", icon: ":refresh:", order: 4 },
138
+ reliability: { label: "Reliability", icon: ":shield:", order: 5 },
139
+ security: { label: "Security", icon: ":lock:", order: 6 },
140
+ custom: { label: "Custom", icon: ":settings:", order: 7 },
137
141
  });
138
142
 
139
143
  export const WORKFLOW_TEMPLATES = Object.freeze([
@@ -150,6 +154,8 @@ export const WORKFLOW_TEMPLATES = Object.freeze([
150
154
  CUSTOM_AGENT_TEMPLATE,
151
155
  AGENT_SESSION_MONITOR_TEMPLATE,
152
156
  BACKEND_AGENT_TEMPLATE,
157
+ VOICE_VIDEO_PARALLEL_ROLLOUT_TEMPLATE,
158
+ MEETING_SUBWORKFLOW_CHAIN_TEMPLATE,
153
159
  // ── Planning ──
154
160
  TASK_PLANNER_TEMPLATE,
155
161
  TASK_REPLENISH_TEMPLATE,
@@ -442,7 +448,7 @@ export const WORKFLOW_SETUP_PROFILES = Object.freeze({
442
448
  workflowAutomationEnabled: true,
443
449
  templateIds: Object.freeze([
444
450
  "template-pr-merge-strategy",
445
- "template-pr-conflict-resolver",
451
+ "template-bosun-pr-watchdog",
446
452
  "template-review-agent",
447
453
  "template-backend-agent",
448
454
  "template-task-planner",