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.
- package/agent-prompts.mjs +95 -0
- package/analyze-agent-work-helpers.mjs +308 -0
- package/analyze-agent-work.mjs +926 -0
- package/autofix.mjs +2 -0
- package/bosun.schema.json +101 -3
- package/codex-shell.mjs +85 -10
- package/desktop/main.mjs +871 -48
- package/desktop/preload.mjs +54 -1
- package/desktop-shortcut.mjs +90 -11
- package/git-editor-fix.mjs +273 -0
- package/mcp-registry.mjs +579 -0
- package/meeting-workflow-service.mjs +631 -0
- package/monitor.mjs +18 -103
- package/package.json +21 -2
- package/primary-agent.mjs +32 -12
- package/session-tracker.mjs +68 -0
- package/setup-web-server.mjs +20 -10
- package/setup.mjs +376 -83
- package/startup-service.mjs +51 -6
- package/stream-resilience.mjs +17 -7
- package/ui/app.js +164 -4
- package/ui/components/agent-selector.js +145 -1
- package/ui/components/chat-view.js +161 -15
- package/ui/components/session-list.js +2 -2
- package/ui/components/shared.js +188 -15
- package/ui/modules/icons.js +13 -0
- package/ui/modules/utils.js +44 -0
- package/ui/modules/voice-client-sdk.js +733 -0
- package/ui/modules/voice-overlay.js +128 -15
- package/ui/modules/voice.js +15 -6
- package/ui/setup.html +281 -81
- package/ui/styles/components.css +99 -3
- package/ui/styles/sessions.css +122 -14
- package/ui/styles.css +14 -0
- package/ui/tabs/agents.js +1 -1
- package/ui/tabs/chat.js +123 -14
- package/ui/tabs/control.js +16 -22
- package/ui/tabs/dashboard.js +85 -8
- package/ui/tabs/library.js +113 -17
- package/ui/tabs/settings.js +116 -2
- package/ui/tabs/tasks.js +388 -39
- package/ui/tabs/telemetry.js +0 -1
- package/ui/tabs/workflows.js +4 -0
- package/ui-server.mjs +400 -22
- package/update-check.mjs +41 -13
- package/voice-action-dispatcher.mjs +844 -0
- package/voice-agents-sdk.mjs +664 -0
- package/voice-auth-manager.mjs +164 -0
- package/voice-relay.mjs +1194 -0
- package/voice-tools.mjs +914 -0
- package/workflow-templates/agents.mjs +6 -2
- package/workflow-templates/github.mjs +154 -12
- package/workflow-templates.mjs +3 -0
- package/github-reconciler.mjs +0 -506
- package/merge-strategy.mjs +0 -1210
- package/pr-cleanup-daemon.mjs +0 -992
- 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: "
|
|
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
|
|
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 (
|
|
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:
|
|
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: "
|
|
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: "
|
|
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:
|
|
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", "
|
|
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:
|
|
539
|
-
"
|
|
540
|
-
"
|
|
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:
|
|
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
|
+
};
|
package/workflow-templates.mjs
CHANGED
|
@@ -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,
|