bosun 0.32.0 → 0.33.0
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/.env.example +5 -5
- package/README.md +3 -0
- package/agent-prompts.mjs +98 -2
- package/anomaly-detector.mjs +54 -0
- package/codex-config.mjs +104 -0
- package/github-app-auth.mjs +7 -7
- package/github-auth-manager.mjs +2 -2
- package/github-oauth-portal.mjs +7 -7
- package/monitor.mjs +3 -3
- package/package.json +13 -4
- package/postinstall.mjs +17 -0
- package/setup-web-server.mjs +388 -5
- package/task-executor.mjs +4 -4
- package/ui/app.js +418 -38
- package/ui/app.monolith.js +4 -4
- package/ui/components/agent-selector.js +119 -11
- package/ui/components/chat-view.js +10 -9
- package/ui/components/diff-viewer.js +13 -12
- package/ui/components/forms.js +1 -1
- package/ui/components/kanban-board.js +172 -15
- package/ui/components/session-list.js +6 -5
- package/ui/components/workspace-switcher.js +5 -4
- package/ui/demo.html +1819 -310
- package/ui/index.html +39 -29
- package/ui/modules/icon-utils.js +183 -0
- package/ui/modules/icons.js +554 -0
- package/ui/modules/router.js +1 -0
- package/ui/modules/settings-schema.js +13 -13
- package/ui/modules/voice.js +294 -0
- package/ui/setup.html +122 -24
- package/ui/styles/base.css +4 -1
- package/ui/styles/components.css +1064 -65
- package/ui/styles/kanban.css +245 -0
- package/ui/styles/layout.css +330 -8
- package/ui/styles/sessions.css +243 -32
- package/ui/styles/variables.css +37 -0
- package/ui/styles/workspace-switcher.css +6 -0
- package/ui/styles.css +6 -0
- package/ui/tabs/agents.js +23 -22
- package/ui/tabs/chat.js +36 -13
- package/ui/tabs/control.js +5 -6
- package/ui/tabs/dashboard.js +19 -12
- package/ui/tabs/infra.js +11 -10
- package/ui/tabs/logs.js +11 -10
- package/ui/tabs/settings.js +17 -16
- package/ui/tabs/tasks.js +618 -48
- package/ui/tabs/workflows.js +1588 -0
- package/ui/vendor/es-module-shims.js +1063 -0
- package/ui/vendor/htm.js +1 -0
- package/ui/vendor/preact-compat.js +2 -0
- package/ui/vendor/preact-hooks.js +2 -0
- package/ui/vendor/preact-signals-core.js +1 -0
- package/ui/vendor/preact-signals.js +1 -0
- package/ui/vendor/preact.js +2 -0
- package/ui-server.mjs +355 -12
- package/vendor-sync.mjs +241 -0
package/.env.example
CHANGED
|
@@ -332,9 +332,9 @@ TELEGRAM_MINIAPP_ENABLED=false
|
|
|
332
332
|
# by the next agent working on that branch. Default: true
|
|
333
333
|
# TASK_UPSTREAM_SYNC_MAIN=true
|
|
334
334
|
|
|
335
|
-
# ─── GitHub App (Bosun[
|
|
336
|
-
# App: https://github.com/apps/bosun-
|
|
337
|
-
# Bot identity: bosun-
|
|
335
|
+
# ─── GitHub App (Bosun[VE] Identity + Auth) ────────────────────────────
|
|
336
|
+
# App: https://github.com/apps/bosun-ve (slug: bosun-ve)
|
|
337
|
+
# Bot identity: bosun-ve[bot] (appears as contributor on every agent commit)
|
|
338
338
|
#
|
|
339
339
|
# Numeric App ID (shown on the App settings page under "About"):
|
|
340
340
|
# BOSUN_GITHUB_APP_ID=2911413
|
|
@@ -350,9 +350,9 @@ TELEGRAM_MINIAPP_ENABLED=false
|
|
|
350
350
|
# BOSUN_GITHUB_WEBHOOK_SECRET=
|
|
351
351
|
#
|
|
352
352
|
# Path to the PEM private key downloaded from App settings → Generate a private key:
|
|
353
|
-
# BOSUN_GITHUB_PRIVATE_KEY_PATH=/path/to/bosun-
|
|
353
|
+
# BOSUN_GITHUB_PRIVATE_KEY_PATH=/path/to/bosun-ve.pem
|
|
354
354
|
#
|
|
355
|
-
# ─── GitHub App Settings (enable all three in https://github.com/settings/apps/bosun-
|
|
355
|
+
# ─── GitHub App Settings (enable all three in https://github.com/settings/apps/bosun-ve) ────
|
|
356
356
|
# ✅ Callback URL → http://127.0.0.1:54317/github/callback (set this FIRST, then Save)
|
|
357
357
|
# ✅ "Request user authorization (OAuth) during installation" → ON
|
|
358
358
|
# GitHub does OAuth at install time, redirecting to the Callback URL with
|
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ bosun --setup
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Requires:
|
|
39
|
+
|
|
39
40
|
- Node.js 18+
|
|
40
41
|
- Git
|
|
41
42
|
- Bash (for `.sh` wrappers) or PowerShell 7+ (for `.ps1` wrappers)
|
|
@@ -60,6 +61,7 @@ Requires:
|
|
|
60
61
|
**Source docs (markdown):** `_docs/` is the source of truth for long-form documentation. The website should be generated from the same markdown content so docs stay in sync.
|
|
61
62
|
|
|
62
63
|
Key references:
|
|
64
|
+
|
|
63
65
|
- [GitHub adapter enhancements](_docs/KANBAN_GITHUB_ENHANCEMENT.md)
|
|
64
66
|
- [GitHub Projects v2 index](_docs/GITHUB_PROJECTS_V2_INDEX.md)
|
|
65
67
|
- [GitHub Projects v2 quickstart](_docs/GITHUB_PROJECTS_V2_QUICKSTART.md)
|
|
@@ -79,6 +81,7 @@ Bosun enforces a strict quality pipeline in both local hooks and CI:
|
|
|
79
81
|
|
|
80
82
|
- **Pre-commit hooks** auto-format and lint staged files.
|
|
81
83
|
- **Pre-push hooks** run targeted checks based on changed files (Go, portal, docs).
|
|
84
|
+
- **Demo load smoke test** runs in `npm test` and blocks push if `site/indexv2.html` or `site/ui/demo.html` fails to load required assets.
|
|
82
85
|
- **Prepublish checks** validate package contents and release readiness.
|
|
83
86
|
|
|
84
87
|
Local commands you can run any time:
|
package/agent-prompts.mjs
CHANGED
|
@@ -119,6 +119,12 @@ const PROMPT_DEFS = [
|
|
|
119
119
|
description:
|
|
120
120
|
"Task management agent prompt with full CRUD access via CLI and REST API.",
|
|
121
121
|
},
|
|
122
|
+
{
|
|
123
|
+
key: "frontendAgent",
|
|
124
|
+
filename: "frontend-agent.md",
|
|
125
|
+
description:
|
|
126
|
+
"Front-end specialist agent with screenshot-based validation and visual verification.",
|
|
127
|
+
},
|
|
122
128
|
];
|
|
123
129
|
|
|
124
130
|
export const AGENT_PROMPT_DEFINITIONS = Object.freeze(
|
|
@@ -604,6 +610,75 @@ Types: feat, fix, docs, refactor, test, chore
|
|
|
604
610
|
Statuses: draft → todo → inprogress → inreview → done
|
|
605
611
|
|
|
606
612
|
See .bosun/agents/task-manager.md for full documentation.
|
|
613
|
+
`,
|
|
614
|
+
frontendAgent: `# Frontend Specialist Agent
|
|
615
|
+
|
|
616
|
+
You are a **front-end development specialist** agent managed by Bosun.
|
|
617
|
+
|
|
618
|
+
## Core Responsibilities
|
|
619
|
+
|
|
620
|
+
1. Implement HTML, CSS, and JavaScript/TypeScript UI changes
|
|
621
|
+
2. Build responsive, accessible UI components
|
|
622
|
+
3. Ensure visual accuracy matching specifications
|
|
623
|
+
4. Validate changes through automated testing AND visual verification
|
|
624
|
+
|
|
625
|
+
## Special Skills
|
|
626
|
+
|
|
627
|
+
- CSS Grid/Flexbox layout
|
|
628
|
+
- Component architecture (React, Preact, Vue, Svelte, vanilla)
|
|
629
|
+
- Responsive design (mobile-first)
|
|
630
|
+
- Accessibility (WCAG 2.1 AA)
|
|
631
|
+
- CSS animations and transitions
|
|
632
|
+
- Design system adherence
|
|
633
|
+
|
|
634
|
+
## CRITICAL: Evidence-Based Validation
|
|
635
|
+
|
|
636
|
+
After completing implementation, you MUST collect visual evidence:
|
|
637
|
+
|
|
638
|
+
### Screenshot Protocol
|
|
639
|
+
1. Start the dev server if not already running
|
|
640
|
+
2. Navigate to every page/component you modified
|
|
641
|
+
3. Take screenshots at THREE viewport sizes:
|
|
642
|
+
- Desktop (1920×1080)
|
|
643
|
+
- Tablet (768×1024)
|
|
644
|
+
- Mobile (375×812)
|
|
645
|
+
4. Save ALL screenshots to \`.bosun/evidence/\` directory
|
|
646
|
+
5. Use descriptive filenames: \`<page>-<viewport>-<timestamp>.png\`
|
|
647
|
+
6. Also screenshot any interactive states (modals, dropdowns, hover states)
|
|
648
|
+
|
|
649
|
+
### Evidence Naming Convention
|
|
650
|
+
\`\`\`
|
|
651
|
+
.bosun/evidence/
|
|
652
|
+
homepage-desktop-1234567890.png
|
|
653
|
+
homepage-tablet-1234567890.png
|
|
654
|
+
homepage-mobile-1234567890.png
|
|
655
|
+
modal-open-desktop-1234567890.png
|
|
656
|
+
dark-mode-desktop-1234567890.png
|
|
657
|
+
\`\`\`
|
|
658
|
+
|
|
659
|
+
## Workflow
|
|
660
|
+
1. Read task requirements and any linked designs/specs
|
|
661
|
+
2. Load relevant skills from \`.bosun/skills/\`
|
|
662
|
+
3. Implement frontend changes
|
|
663
|
+
4. Run build: \`npm run build\` (zero errors AND zero warnings)
|
|
664
|
+
5. Run lint: \`npm run lint\`
|
|
665
|
+
6. Run tests: \`npm test\`
|
|
666
|
+
7. Start dev server and collect screenshots (see protocol above)
|
|
667
|
+
8. Commit with conventional format: \`feat(ui): ...\` or \`fix(ui): ...\`
|
|
668
|
+
9. Push branch
|
|
669
|
+
|
|
670
|
+
## IMPORTANT: Do NOT mark the task complete
|
|
671
|
+
The Bosun workflow engine handles completion verification.
|
|
672
|
+
An independent model will review your screenshots against the task
|
|
673
|
+
requirements before the task is marked as done.
|
|
674
|
+
|
|
675
|
+
## Task Context
|
|
676
|
+
- Task: {{TASK_TITLE}}
|
|
677
|
+
- Description: {{TASK_DESCRIPTION}}
|
|
678
|
+
- Branch: {{BRANCH}}
|
|
679
|
+
- Working Directory: {{WORKTREE_PATH}}
|
|
680
|
+
|
|
681
|
+
{{COAUTHOR_INSTRUCTION}}
|
|
607
682
|
`,
|
|
608
683
|
};
|
|
609
684
|
|
|
@@ -670,19 +745,40 @@ export function getDefaultPromptTemplate(key) {
|
|
|
670
745
|
return DEFAULT_PROMPTS[key] || "";
|
|
671
746
|
}
|
|
672
747
|
|
|
673
|
-
export function renderPromptTemplate(template, values = {}) {
|
|
748
|
+
export function renderPromptTemplate(template, values = {}, rootDir) {
|
|
674
749
|
if (typeof template !== "string") return "";
|
|
675
750
|
const normalized = {};
|
|
676
751
|
for (const [k, v] of Object.entries(values || {})) {
|
|
677
752
|
normalized[String(k).trim().toUpperCase()] = normalizeTemplateValue(v);
|
|
678
753
|
}
|
|
679
754
|
|
|
680
|
-
|
|
755
|
+
// Resolve namespaced library refs: {{prompt:name}}, {{agent:name}}, {{skill:name}}
|
|
756
|
+
let result = template;
|
|
757
|
+
if (rootDir && _libraryResolver) {
|
|
758
|
+
try {
|
|
759
|
+
result = _libraryResolver(result, rootDir, {});
|
|
760
|
+
} catch {
|
|
761
|
+
// library resolver failed — skip namespaced refs
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return result.replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g, (full, key) => {
|
|
681
766
|
const hit = normalized[String(key).toUpperCase()];
|
|
682
767
|
return hit == null ? "" : hit;
|
|
683
768
|
});
|
|
684
769
|
}
|
|
685
770
|
|
|
771
|
+
/**
|
|
772
|
+
* Register the library reference resolver. Called by library-manager or at
|
|
773
|
+
* startup to enable {{prompt:name}}, {{agent:name}}, {{skill:name}} syntax.
|
|
774
|
+
*
|
|
775
|
+
* @param {(template: string, rootDir: string, vars: Object) => string} resolver
|
|
776
|
+
*/
|
|
777
|
+
let _libraryResolver = null;
|
|
778
|
+
export function setLibraryResolver(resolver) {
|
|
779
|
+
_libraryResolver = typeof resolver === "function" ? resolver : null;
|
|
780
|
+
}
|
|
781
|
+
|
|
686
782
|
export function resolvePromptTemplate(template, values, fallback) {
|
|
687
783
|
const base = typeof fallback === "string" ? fallback : "";
|
|
688
784
|
if (typeof template !== "string" || !template.trim()) return base;
|
package/anomaly-detector.mjs
CHANGED
|
@@ -1211,3 +1211,57 @@ export function createAnomalyDetector(options = {}) {
|
|
|
1211
1211
|
*/
|
|
1212
1212
|
|
|
1213
1213
|
export default AnomalyDetector;
|
|
1214
|
+
|
|
1215
|
+
// ── Workflow Engine Integration ─────────────────────────────────────────────
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Registered workflow engine reference for anomaly → workflow triggers.
|
|
1219
|
+
* @type {import("./workflow-engine.mjs").WorkflowEngine | null}
|
|
1220
|
+
*/
|
|
1221
|
+
let _workflowEngine = null;
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Register a workflow engine to receive anomaly trigger events.
|
|
1225
|
+
* When set, every anomaly emitted will also call engine.evaluateTriggers("anomaly", anomalyData).
|
|
1226
|
+
*
|
|
1227
|
+
* @param {import("./workflow-engine.mjs").WorkflowEngine} engine
|
|
1228
|
+
*/
|
|
1229
|
+
export function setWorkflowEngine(engine) {
|
|
1230
|
+
_workflowEngine = engine;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/** Get the currently registered workflow engine (or null) */
|
|
1234
|
+
export function getWorkflowEngine() {
|
|
1235
|
+
return _workflowEngine;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Create an onAnomaly callback that also fires workflow triggers.
|
|
1240
|
+
* Wraps the user's original callback and additionally invokes
|
|
1241
|
+
* evaluateTriggers on the registered workflow engine.
|
|
1242
|
+
*
|
|
1243
|
+
* @param {(anomaly: Anomaly) => void} [originalCallback]
|
|
1244
|
+
* @returns {(anomaly: Anomaly) => void}
|
|
1245
|
+
*/
|
|
1246
|
+
export function wrapAnomalyCallback(originalCallback) {
|
|
1247
|
+
return (anomaly) => {
|
|
1248
|
+
// Always call the original callback
|
|
1249
|
+
if (originalCallback) originalCallback(anomaly);
|
|
1250
|
+
|
|
1251
|
+
// Fire workflow triggers if engine is registered
|
|
1252
|
+
if (_workflowEngine?.evaluateTriggers) {
|
|
1253
|
+
try {
|
|
1254
|
+
_workflowEngine.evaluateTriggers("anomaly", {
|
|
1255
|
+
anomalyType: anomaly.type,
|
|
1256
|
+
severity: anomaly.severity,
|
|
1257
|
+
agentId: anomaly.processId || anomaly.shortId,
|
|
1258
|
+
message: anomaly.message,
|
|
1259
|
+
action: anomaly.action,
|
|
1260
|
+
taskTitle: anomaly.taskTitle,
|
|
1261
|
+
data: anomaly.data,
|
|
1262
|
+
timestamp: Date.now(),
|
|
1263
|
+
});
|
|
1264
|
+
} catch { /* workflow trigger errors should not break anomaly detection */ }
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
}
|
package/codex-config.mjs
CHANGED
|
@@ -1618,6 +1618,110 @@ export function printConfigSummary(result, log = console.log) {
|
|
|
1618
1618
|
log(` Config: ${result.path}`);
|
|
1619
1619
|
}
|
|
1620
1620
|
|
|
1621
|
+
// ── Trusted Projects ─────────────────────────────────────────────────────────
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* Escape a string for use inside a double-quoted TOML basic string.
|
|
1625
|
+
* Handles backslashes (Windows paths) and double-quote characters.
|
|
1626
|
+
*/
|
|
1627
|
+
function tomlEscapeStr(s) {
|
|
1628
|
+
return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Format an array of strings as a TOML array literal, correctly escaping
|
|
1633
|
+
* backslashes so Windows paths are stored faithfully.
|
|
1634
|
+
*
|
|
1635
|
+
* Example output: ["C:\\Users\\jon\\bosun", "/home/jon/bosun"]
|
|
1636
|
+
*/
|
|
1637
|
+
function formatTomlArrayEscaped(values) {
|
|
1638
|
+
return `[${values.map((v) => `"${tomlEscapeStr(v)}"`).join(", ")}]`;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/**
|
|
1642
|
+
* Parse a TOML basic-string array literal, unescaping backslash sequences.
|
|
1643
|
+
*/
|
|
1644
|
+
function parseTomlArrayLiteralEscaped(raw) {
|
|
1645
|
+
if (!raw) return [];
|
|
1646
|
+
const inner = raw.trim().replace(/^\[/, "").replace(/\]$/, "");
|
|
1647
|
+
if (!inner.trim()) return [];
|
|
1648
|
+
// Split on commas that are NOT inside quotes
|
|
1649
|
+
const items = [];
|
|
1650
|
+
let buf = "";
|
|
1651
|
+
let inStr = false;
|
|
1652
|
+
for (let i = 0; i < inner.length; i++) {
|
|
1653
|
+
const ch = inner[i];
|
|
1654
|
+
if (ch === "\\" && inStr) { buf += ch + (inner[++i] || ""); continue; }
|
|
1655
|
+
if (ch === '"') { inStr = !inStr; buf += ch; continue; }
|
|
1656
|
+
if (ch === "," && !inStr) { items.push(buf.trim()); buf = ""; continue; }
|
|
1657
|
+
buf += ch;
|
|
1658
|
+
}
|
|
1659
|
+
if (buf.trim()) items.push(buf.trim());
|
|
1660
|
+
return items
|
|
1661
|
+
.map((item) => item.replace(/^"(.*)"$/s, "$1")) // strip outer quotes
|
|
1662
|
+
.map((item) => item.replace(/\\(["\\])/g, "$1")) // unescape \" and \\
|
|
1663
|
+
.filter(Boolean);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* Ensure the given directory paths are listed in the `trusted_projects`
|
|
1668
|
+
* top-level key in ~/.codex/config.toml.
|
|
1669
|
+
*
|
|
1670
|
+
* Codex refuses to load a per-project .codex/config.toml unless the project
|
|
1671
|
+
* directory appears in this list — producing warnings like:
|
|
1672
|
+
* "⚠ Project config.toml files are disabled … add <dir> as a trusted project"
|
|
1673
|
+
*
|
|
1674
|
+
* Paths are stored as-is (forward or back slashes preserved) with proper TOML
|
|
1675
|
+
* escaping so Windows paths survive round-trips through the file.
|
|
1676
|
+
*
|
|
1677
|
+
* @param {string[]} paths Absolute directories to trust (e.g. [bosunHome])
|
|
1678
|
+
* @param {{ dryRun?: boolean }} [opts]
|
|
1679
|
+
* @returns {{ added: string[], already: string[], path: string }}
|
|
1680
|
+
*/
|
|
1681
|
+
export function ensureTrustedProjects(paths, { dryRun = false } = {}) {
|
|
1682
|
+
const result = { added: [], already: [], path: CONFIG_PATH };
|
|
1683
|
+
const desired = (paths || []).map((p) => resolve(p)).filter(Boolean);
|
|
1684
|
+
if (desired.length === 0) return result;
|
|
1685
|
+
|
|
1686
|
+
let toml = readCodexConfig() || "";
|
|
1687
|
+
|
|
1688
|
+
// Parse existing trusted_projects (multi-line arrays may span lines)
|
|
1689
|
+
const existingMatch = toml.match(/^trusted_projects\s*=\s*(\[[^\]]*\])/m);
|
|
1690
|
+
const existing = existingMatch ? parseTomlArrayLiteralEscaped(existingMatch[1]) : [];
|
|
1691
|
+
|
|
1692
|
+
let changed = false;
|
|
1693
|
+
for (const p of desired) {
|
|
1694
|
+
if (existing.includes(p)) {
|
|
1695
|
+
result.already.push(p);
|
|
1696
|
+
} else {
|
|
1697
|
+
existing.push(p);
|
|
1698
|
+
result.added.push(p);
|
|
1699
|
+
changed = true;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
if (!changed) return result;
|
|
1704
|
+
if (dryRun) return result;
|
|
1705
|
+
|
|
1706
|
+
const newLine = `trusted_projects = ${formatTomlArrayEscaped(existing)}`;
|
|
1707
|
+
|
|
1708
|
+
if (existingMatch) {
|
|
1709
|
+
toml = toml.replace(/^trusted_projects\s*=\s*\[[^\]]*\]/m, newLine);
|
|
1710
|
+
} else {
|
|
1711
|
+
// Insert before the first section header (or at top if no sections)
|
|
1712
|
+
const firstSection = toml.search(/^\[/m);
|
|
1713
|
+
if (firstSection === -1) {
|
|
1714
|
+
toml = `${newLine}\n${toml}`;
|
|
1715
|
+
} else {
|
|
1716
|
+
toml = `${toml.slice(0, firstSection)}${newLine}\n\n${toml.slice(firstSection)}`;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
mkdirSync(CODEX_DIR, { recursive: true });
|
|
1721
|
+
writeFileSync(CONFIG_PATH, toml, "utf8");
|
|
1722
|
+
return result;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1621
1725
|
// ── Internal Helpers ─────────────────────────────────────────────────────────
|
|
1622
1726
|
|
|
1623
1727
|
function escapeRegex(str) {
|
package/github-app-auth.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* github-app-auth.mjs — GitHub App JWT + Installation Token helpers
|
|
4
4
|
*
|
|
5
|
-
* Provides credential helpers for the Bosun[
|
|
5
|
+
* Provides credential helpers for the Bosun[VE] GitHub App:
|
|
6
6
|
* - signAppJWT() — RS256 JWT proving Bosun IS the app
|
|
7
7
|
* - getInstallationToken(installationId) — short-lived install access token
|
|
8
8
|
* - getInstallationTokenForRepo(owner,repo) — auto-resolves install from repo
|
|
@@ -90,7 +90,7 @@ export async function getInstallationToken(installationId) {
|
|
|
90
90
|
Authorization: `Bearer ${jwt}`,
|
|
91
91
|
Accept: "application/vnd.github+json",
|
|
92
92
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
93
|
-
"User-Agent": "bosun-
|
|
93
|
+
"User-Agent": "bosun-ve",
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
96
|
);
|
|
@@ -123,7 +123,7 @@ export async function getInstallationTokenForRepo(owner, repo) {
|
|
|
123
123
|
Authorization: `Bearer ${jwt}`,
|
|
124
124
|
Accept: "application/vnd.github+json",
|
|
125
125
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
126
|
-
"User-Agent": "bosun-
|
|
126
|
+
"User-Agent": "bosun-ve",
|
|
127
127
|
},
|
|
128
128
|
},
|
|
129
129
|
);
|
|
@@ -163,7 +163,7 @@ export async function startDeviceFlow(scope = "repo") {
|
|
|
163
163
|
headers: {
|
|
164
164
|
Accept: "application/json",
|
|
165
165
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
166
|
-
"User-Agent": "bosun-
|
|
166
|
+
"User-Agent": "bosun-ve",
|
|
167
167
|
},
|
|
168
168
|
body: body.toString(),
|
|
169
169
|
});
|
|
@@ -217,7 +217,7 @@ export async function pollDeviceToken(deviceCode) {
|
|
|
217
217
|
headers: {
|
|
218
218
|
Accept: "application/json",
|
|
219
219
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
220
|
-
"User-Agent": "bosun-
|
|
220
|
+
"User-Agent": "bosun-ve",
|
|
221
221
|
},
|
|
222
222
|
body: body.toString(),
|
|
223
223
|
});
|
|
@@ -281,7 +281,7 @@ export async function exchangeOAuthCode(code) {
|
|
|
281
281
|
headers: {
|
|
282
282
|
Accept: "application/json",
|
|
283
283
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
284
|
-
"User-Agent": "bosun-
|
|
284
|
+
"User-Agent": "bosun-ve",
|
|
285
285
|
},
|
|
286
286
|
body: body.toString(),
|
|
287
287
|
});
|
|
@@ -318,7 +318,7 @@ export async function getOAuthUser(accessToken) {
|
|
|
318
318
|
Authorization: `Bearer ${accessToken}`,
|
|
319
319
|
Accept: "application/vnd.github+json",
|
|
320
320
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
321
|
-
"User-Agent": "bosun-
|
|
321
|
+
"User-Agent": "bosun-ve",
|
|
322
322
|
},
|
|
323
323
|
});
|
|
324
324
|
if (!res.ok) {
|
package/github-auth-manager.mjs
CHANGED
|
@@ -82,7 +82,7 @@ async function verifyToken(token) {
|
|
|
82
82
|
Authorization: `Bearer ${token}`,
|
|
83
83
|
Accept: "application/vnd.github+json",
|
|
84
84
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
85
|
-
"User-Agent": "bosun-
|
|
85
|
+
"User-Agent": "bosun-ve",
|
|
86
86
|
},
|
|
87
87
|
});
|
|
88
88
|
if (!res.ok) return null;
|
|
@@ -162,7 +162,7 @@ export async function getAuthHeaders(options = {}) {
|
|
|
162
162
|
Authorization: `Bearer ${token}`,
|
|
163
163
|
Accept: "application/vnd.github+json",
|
|
164
164
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
165
|
-
"User-Agent": "bosun-
|
|
165
|
+
"User-Agent": "bosun-ve",
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
168
|
|
package/github-oauth-portal.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* github-oauth-portal.mjs — Self-contained OAuth setup portal for Bosun[
|
|
2
|
+
* github-oauth-portal.mjs — Self-contained OAuth setup portal for Bosun[VE]
|
|
3
3
|
*
|
|
4
4
|
* Serves a local HTTP portal on port 54317 (bound to 127.0.0.1) that guides
|
|
5
5
|
* users through GitHub App installation and OAuth authorisation.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* GET /api/status — JSON status endpoint
|
|
14
14
|
* GET /api/installations — list App installations (requires App JWT)
|
|
15
15
|
*
|
|
16
|
-
* GitHub App settings (https://github.com/settings/apps/bosun-
|
|
16
|
+
* GitHub App settings (https://github.com/settings/apps/bosun-ve):
|
|
17
17
|
* Callback URL: http://127.0.0.1:54317/github/callback ← ONLY URL needed
|
|
18
18
|
*
|
|
19
19
|
* Notes:
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
|
|
55
55
|
const DEFAULT_PORT = 54317;
|
|
56
56
|
const DEFAULT_HOST = "127.0.0.1";
|
|
57
|
-
const GITHUB_APP_NAME = "bosun-
|
|
57
|
+
const GITHUB_APP_NAME = "bosun-ve";
|
|
58
58
|
const GITHUB_APP_URL = `https://github.com/apps/${GITHUB_APP_NAME}`;
|
|
59
59
|
const GITHUB_APP_INSTALL_URL = `${GITHUB_APP_URL}/installations/new`;
|
|
60
60
|
const STATE_FILE = join(homedir(), ".bosun", "github-auth-state.json");
|
|
@@ -271,7 +271,7 @@ function htmlPage(title, bodyHtml) {
|
|
|
271
271
|
<body>
|
|
272
272
|
<div class="logo">⚓</div>
|
|
273
273
|
<h1>Bosun</h1>
|
|
274
|
-
<div class="subtitle">GitHub App OAuth Setup Portal · <code>bosun-
|
|
274
|
+
<div class="subtitle">GitHub App OAuth Setup Portal · <code>bosun-ve</code></div>
|
|
275
275
|
${bodyHtml}
|
|
276
276
|
<script>
|
|
277
277
|
function copyToClipboard(text, btn) {
|
|
@@ -569,7 +569,7 @@ async function handleSetup(req, res) {
|
|
|
569
569
|
Authorization: `Bearer ${jwt}`,
|
|
570
570
|
Accept: "application/vnd.github+json",
|
|
571
571
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
572
|
-
"User-Agent": "bosun-
|
|
572
|
+
"User-Agent": "bosun-ve",
|
|
573
573
|
},
|
|
574
574
|
},
|
|
575
575
|
);
|
|
@@ -703,7 +703,7 @@ function handleWebhookEvent(eventType, payload) {
|
|
|
703
703
|
}
|
|
704
704
|
|
|
705
705
|
const CMD_RE = /\/bosun[ \t]+(\w+)(?:[ \t]+(\S+))?/g;
|
|
706
|
-
const MENTION_RE = /@bosun-
|
|
706
|
+
const MENTION_RE = /@bosun-ve/i;
|
|
707
707
|
|
|
708
708
|
function processBosunCommand(body, payload) {
|
|
709
709
|
if (MENTION_RE.test(body)) {
|
|
@@ -766,7 +766,7 @@ async function handleApiInstallations(req, res) {
|
|
|
766
766
|
Authorization: `Bearer ${jwt}`,
|
|
767
767
|
Accept: "application/vnd.github+json",
|
|
768
768
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
769
|
-
"User-Agent": "bosun-
|
|
769
|
+
"User-Agent": "bosun-ve",
|
|
770
770
|
},
|
|
771
771
|
});
|
|
772
772
|
|
package/monitor.mjs
CHANGED
|
@@ -3339,9 +3339,9 @@ async function createPRViaVK(attemptId, prOpts = {}) {
|
|
|
3339
3339
|
return { success: false, error: "repo_id_missing", _elapsedMs: 0 };
|
|
3340
3340
|
}
|
|
3341
3341
|
|
|
3342
|
-
const bosunCredit = "\n\n---\n*Created by [Bosun Bot](https://github.com/apps/bosun-
|
|
3342
|
+
const bosunCredit = "\n\n---\n*Created by [Bosun Bot](https://github.com/apps/bosun-ve)*";
|
|
3343
3343
|
const rawDescription = prOpts.description || "";
|
|
3344
|
-
const description = rawDescription.includes("bosun-
|
|
3344
|
+
const description = rawDescription.includes("bosun-ve")
|
|
3345
3345
|
? rawDescription
|
|
3346
3346
|
: rawDescription.trimEnd() + bosunCredit;
|
|
3347
3347
|
|
|
@@ -5386,7 +5386,7 @@ function buildEpicMergeBody(tasks, headName, baseName) {
|
|
|
5386
5386
|
}
|
|
5387
5387
|
lines.push("");
|
|
5388
5388
|
lines.push("---");
|
|
5389
|
-
lines.push("*Created by [Bosun Bot](https://github.com/apps/bosun-
|
|
5389
|
+
lines.push("*Created by [Bosun Bot](https://github.com/apps/bosun-ve)*");
|
|
5390
5390
|
return lines.join("\n");
|
|
5391
5391
|
}
|
|
5392
5392
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "AI-powered orchestrator supervisor — manages AI agent executors with failover, auto-restarts on failure, analyzes crashes with Codex SDK, creates PRs via Vibe-Kanban API, and sends Telegram notifications. Supports N executors with weighted distribution, multi-repo projects, and auto-setup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache 2.0",
|
|
@@ -67,7 +67,6 @@
|
|
|
67
67
|
"./container-runner": "./container-runner.mjs",
|
|
68
68
|
"./compat": "./compat.mjs",
|
|
69
69
|
"./task-cli": "./task-cli.mjs",
|
|
70
|
-
"./github-oauth-portal": "./github-oauth-portal.mjs",
|
|
71
70
|
"./github-auth-manager": "./github-auth-manager.mjs",
|
|
72
71
|
"./git-commit-helpers": "./git-commit-helpers.mjs"
|
|
73
72
|
},
|
|
@@ -91,6 +90,7 @@
|
|
|
91
90
|
"desktop": "node cli.mjs --desktop",
|
|
92
91
|
"desktop:install": "npm -C desktop install",
|
|
93
92
|
"desktop:dist": "npm -C desktop run dist",
|
|
93
|
+
"build": "node vendor-sync.mjs",
|
|
94
94
|
"shared-workspaces": "node shared-workspace-cli.mjs",
|
|
95
95
|
"syntax:check": "node -e \"const fs=require('fs'),path=require('path');const files=fs.readdirSync('.').filter(f=>f.endsWith('.mjs'));let fail=0;for(const f of files){try{require('child_process').execSync('node --check '+f,{stdio:'pipe'});}catch(e){console.error('Syntax error: '+f);console.error(e.stderr.toString());fail=1;}}if(fail)process.exit(1);console.log('Syntax OK: '+files.length+' files checked');\"",
|
|
96
96
|
"pretest": "npm run syntax:check",
|
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
"test:watch": "vitest",
|
|
99
99
|
"preinstall": "node -e \"try{var r=require('child_process').execSync('npm ls -g codex-monitor --json --depth=0',{encoding:'utf8',stdio:['pipe','pipe','pipe']});var d=JSON.parse(r).dependencies;if(d&&d['codex-monitor']){console.log('\\n Removing old codex-monitor package...');require('child_process').execSync('npm uninstall -g codex-monitor',{stdio:'inherit',timeout:30000});console.log(' \\u2705 Migrated to bosun. codex-monitor aliases still work.\\n')}}catch(e){}\"",
|
|
100
100
|
"postinstall": "node postinstall.mjs",
|
|
101
|
+
"prepare": "node vendor-sync.mjs",
|
|
101
102
|
"sentinel": "node telegram-sentinel.mjs",
|
|
102
103
|
"sentinel:stop": "node -e \"import('./telegram-sentinel.mjs').then(m => m.stopSentinel())\"",
|
|
103
104
|
"sentinel:status": "node -e \"import('./telegram-sentinel.mjs').then(m => console.log(JSON.stringify(m.getSentinelStatus(), null, 2)))\"",
|
|
@@ -108,7 +109,9 @@
|
|
|
108
109
|
"publish:minor": "node publish.mjs --bump minor",
|
|
109
110
|
"publish:major": "node publish.mjs --bump major",
|
|
110
111
|
"publish:minor:dry": "node publish.mjs --bump minor --dry-run",
|
|
111
|
-
"publish:major:dry": "node publish.mjs --bump major --dry-run"
|
|
112
|
+
"publish:major:dry": "node publish.mjs --bump major --dry-run",
|
|
113
|
+
"vendorsync": "node vendor-sync.mjs",
|
|
114
|
+
"vendor:sync": "node vendor-sync.mjs"
|
|
112
115
|
},
|
|
113
116
|
"files": [
|
|
114
117
|
".env.example",
|
|
@@ -212,14 +215,20 @@
|
|
|
212
215
|
"whatsapp-channel.mjs",
|
|
213
216
|
"container-runner.mjs",
|
|
214
217
|
"daemon-restart-policy.mjs",
|
|
215
|
-
"publish.mjs"
|
|
218
|
+
"publish.mjs",
|
|
219
|
+
"vendor-sync.mjs",
|
|
220
|
+
"ui/vendor/"
|
|
216
221
|
],
|
|
217
222
|
"dependencies": {
|
|
218
223
|
"@anthropic-ai/claude-agent-sdk": "latest",
|
|
219
224
|
"@github/copilot-sdk": "latest",
|
|
220
225
|
"@openai/codex-sdk": "latest",
|
|
226
|
+
"@preact/signals": "1.3.1",
|
|
221
227
|
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
|
222
228
|
"ajv": "^8.18.0",
|
|
229
|
+
"es-module-shims": "1.10.0",
|
|
230
|
+
"htm": "3.1.1",
|
|
231
|
+
"preact": "10.25.4",
|
|
223
232
|
"qrcode-terminal": "^0.12.0",
|
|
224
233
|
"vibe-kanban": "latest",
|
|
225
234
|
"ws": "^8.19.0"
|
package/postinstall.mjs
CHANGED
|
@@ -396,6 +396,23 @@ async function main() {
|
|
|
396
396
|
} catch {
|
|
397
397
|
// Non-blocking; hooks can be installed via `npm run hooks:install`
|
|
398
398
|
}
|
|
399
|
+
|
|
400
|
+
// Sync vendor files into ui/vendor/ so the UI works fully offline.
|
|
401
|
+
// Non-blocking — a missing vendor file just falls back to node_modules or CDN.
|
|
402
|
+
try {
|
|
403
|
+
const { syncVendorFiles } = await import("./vendor-sync.mjs");
|
|
404
|
+
const { ok, results } = await syncVendorFiles({ silent: true });
|
|
405
|
+
const synced = results.filter((r) => r.source).length;
|
|
406
|
+
if (ok) {
|
|
407
|
+
console.log(` ✅ Vendor files bundled into ui/vendor/ (${synced}/${results.length} files)`);
|
|
408
|
+
} else {
|
|
409
|
+
const missing = results.filter((r) => !r.source).map((r) => r.name);
|
|
410
|
+
console.warn(` ⚠️ Some vendor files could not be bundled: ${missing.join(", ")}`);
|
|
411
|
+
console.warn(" The UI server will fall back to CDN for those files.");
|
|
412
|
+
}
|
|
413
|
+
} catch (err) {
|
|
414
|
+
console.warn(` ⚠️ vendor-sync skipped: ${err.message}`);
|
|
415
|
+
}
|
|
399
416
|
}
|
|
400
417
|
|
|
401
418
|
main().catch((err) => {
|