bosun 0.39.2 → 0.40.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 +4 -0
- package/README.md +31 -32
- package/agent/agent-custom-tools.mjs +19 -3
- package/agent/agent-pool.mjs +14 -5
- package/agent/agent-prompts.mjs +2 -2
- package/agent/bosun-skills.mjs +83 -14
- package/agent/primary-agent.mjs +77 -0
- package/bosun.schema.json +22 -0
- package/cli.mjs +287 -31
- package/desktop/launch.mjs +44 -6
- package/desktop/main.mjs +474 -33
- package/desktop/package.json +2 -2
- package/infra/library-manager.mjs +694 -33
- package/infra/monitor.mjs +354 -5
- package/infra/session-tracker.mjs +30 -0
- package/monitor-tail-sanitizer.mjs +77 -0
- package/package.json +6 -3
- package/server/ui-server.mjs +1194 -55
- package/shell/copilot-shell.mjs +38 -11
- package/task/task-cli.mjs +105 -9
- package/task/task-executor.mjs +96 -12
- package/task/task-store.mjs +1362 -46
- package/telegram/telegram-bot.mjs +64 -10
- package/tools/generate-demo-defaults.mjs +263 -0
- package/tools/packed-cli-smoke.mjs +152 -0
- package/tools/prepublish-check.mjs +113 -67
- package/tools/test-shared-state-integration.mjs +5 -5
- package/ui/components/chat-view.js +12 -2
- package/ui/components/kanban-board.js +144 -39
- package/ui/components/shared.js +17 -8
- package/ui/demo-defaults.js +32549 -0
- package/ui/demo.html +275 -8
- package/ui/modules/mui.js +58 -25
- package/ui/modules/settings-schema.js +3 -0
- package/ui/modules/state.js +129 -3
- package/ui/styles/kanban.css +57 -132
- package/ui/styles/sessions.css +6 -0
- package/ui/tabs/library.js +140 -29
- package/ui/tabs/tasks.js +1019 -134
- package/workflow/mcp-discovery-proxy.mjs +629 -0
- package/workflow/mcp-registry.mjs +67 -1
- package/workflow/workflow-engine.mjs +354 -2
- package/workflow/workflow-nodes.mjs +524 -19
- package/workflow/workflow-templates.mjs +6 -1
- package/workflow-templates/planning.mjs +114 -1
package/.env.example
CHANGED
|
@@ -348,6 +348,10 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
|
|
|
348
348
|
# INTERNAL_EXECUTOR_BASE_BRANCH_PARALLEL=0
|
|
349
349
|
# How often to poll kanban for new tasks in ms (default: 30000)
|
|
350
350
|
# INTERNAL_EXECUTOR_POLL_MS=30000
|
|
351
|
+
# Fail-fast policy for workflow start guards when a task appears missing in task-store.
|
|
352
|
+
# false (default): allow dispatch and emit start_guard_bypass audit event
|
|
353
|
+
# true: block dispatch and emit start_guard_blocked audit event
|
|
354
|
+
# BOSUN_STRICT_START_GUARD_MISSING_TASK=false
|
|
351
355
|
# SDK to use: "auto" | "codex" | "copilot" | "claude" | "gemini" | "opencode" (default: auto)
|
|
352
356
|
# INTERNAL_EXECUTOR_SDK=auto
|
|
353
357
|
# Timeout per task execution in ms (default: 5400000 = 90 min)
|
package/README.md
CHANGED
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
Bosun is a production-grade control plane for an autonomous software engineer. It plans and routes work across executors, automates PR lifecycles, and keeps operators in control through Telegram, the Mini App dashboard, and optional WhatsApp notifications.
|
|
7
7
|
|
|
8
|
+
## Why "Bosun"?
|
|
9
|
+
|
|
10
|
+
_The name "Bosun" comes from "boatswain", the ship's officer responsible for coordinating deck work, keeping operations moving, and translating command into disciplined execution._
|
|
11
|
+
|
|
12
|
+
_That maps directly to the Bosun project: it does not replace the captain or crew, it orchestrates the work. Our Bosun plans tasks, routes them to the right executors, enforces operational checks, and keeps humans in control while the system keeps delivery moving. Autonomous engineering with you in control of the operation._
|
|
13
|
+
|
|
8
14
|
<p align="center">
|
|
9
15
|
<a href="https://bosun.engineer">Website</a> · <a href="https://bosun.engineer/docs/">Docs</a> · <a href="https://github.com/virtengine/bosun?tab=readme-ov-file#bosun">GitHub</a> · <a href="https://www.npmjs.com/package/bosun">npm</a> · <a href="https://github.com/virtengine/bosun/issues">Issues</a>
|
|
10
16
|
</p>
|
|
11
17
|
|
|
12
18
|
<p align="center">
|
|
13
|
-
<img src="site/
|
|
19
|
+
<img src="site/workflows.png" alt="Bosun Workflows for Autonomous Engineering" width="100%" />
|
|
14
20
|
</p>
|
|
15
21
|
|
|
16
22
|
<p align="center">
|
|
@@ -105,18 +111,10 @@ Setup profiles for default workflow behavior:
|
|
|
105
111
|
| Codex (OpenAI) | `codex-sdk` | `OPENAI_API_KEY` |
|
|
106
112
|
| Copilot (VS Code) | `copilot-sdk` | VS Code session |
|
|
107
113
|
| Claude | `claude-sdk` | `ANTHROPIC_API_KEY` |
|
|
108
|
-
| OpenCode | `opencode-sdk` | `OPENCODE_MODEL` (e.g. `anthropic/claude-opus-4-
|
|
114
|
+
| OpenCode | `opencode-sdk` | `OPENCODE_MODEL` (e.g. `anthropic/claude-opus-4-6`), `OPENCODE_PORT` (default `4096`) |
|
|
109
115
|
|
|
110
116
|
Set `primaryAgent` in `.bosun/bosun.config.json` or choose an executor preset during `bosun --setup`.
|
|
111
117
|
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Telegram weekly report
|
|
115
|
-
|
|
116
|
-
- Run `/weekly` to generate the operator weekly agent-work report on demand.
|
|
117
|
-
- Use `/report weekly` as an alias.
|
|
118
|
-
- Optional scheduler knobs in `.env`: `TELEGRAM_WEEKLY_REPORT_ENABLED`, `TELEGRAM_WEEKLY_REPORT_DAY`, `TELEGRAM_WEEKLY_REPORT_HOUR` (UTC), and `TELEGRAM_WEEKLY_REPORT_DAYS`.
|
|
119
|
-
|
|
120
118
|
## Daemon and sentinel startup
|
|
121
119
|
|
|
122
120
|
- `bosun --daemon` starts the long-running daemon/monitor.
|
|
@@ -130,19 +128,15 @@ Set `primaryAgent` in `.bosun/bosun.config.json` or choose an executor preset du
|
|
|
130
128
|
|
|
131
129
|
**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.
|
|
132
130
|
|
|
133
|
-
|
|
131
|
+
**Product docs and implementation notes:** `docs/` contains focused guides, design notes, and operator-facing references that are kept alongside the codebase.
|
|
134
132
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
- [Workflows](_docs/WORKFLOWS.md)
|
|
143
|
-
- [Agent logging quickstart](docs/agent-logging-quickstart.md)
|
|
144
|
-
- [Agent logging design](docs/agent-work-logging-design.md)
|
|
145
|
-
- [Agent logging summary](docs/AGENT_LOGGING_SUMMARY.md)
|
|
133
|
+
Key places to start:
|
|
134
|
+
|
|
135
|
+
- `README.md` - install, setup, and operational overview
|
|
136
|
+
- `_docs/WORKFLOWS.md` - workflow system and built-in templates
|
|
137
|
+
- `docs/workflows-and-libraries.md` - workflow composition and library behavior
|
|
138
|
+
- `docs/agent-logging-quickstart.md` - agent work logging quickstart
|
|
139
|
+
- `docs/agent-work-logging-design.md` - logging design and event model
|
|
146
140
|
|
|
147
141
|
---
|
|
148
142
|
|
|
@@ -152,7 +146,7 @@ Bosun enforces a strict quality pipeline in both local hooks and CI:
|
|
|
152
146
|
|
|
153
147
|
- **Pre-commit hooks** auto-format and lint staged files.
|
|
154
148
|
- **Pre-push hooks** run targeted checks based on changed files (Go, portal, docs).
|
|
155
|
-
- **Demo load smoke test** runs in `npm test` and blocks push if `site/
|
|
149
|
+
- **Demo load smoke test** runs in `npm test` and blocks push if `site/index.html` or `site/ui/demo.html` fails to load required assets.
|
|
156
150
|
- **Prepublish checks** validate package contents and release readiness.
|
|
157
151
|
|
|
158
152
|
Local commands you can run any time:
|
|
@@ -172,18 +166,23 @@ npm run hooks:install
|
|
|
172
166
|
|
|
173
167
|
## Repository layout
|
|
174
168
|
|
|
175
|
-
- `cli.mjs` — entrypoint for
|
|
176
|
-
- `
|
|
177
|
-
- `
|
|
178
|
-
- `
|
|
179
|
-
- `
|
|
180
|
-
- `
|
|
169
|
+
- `cli.mjs` — CLI entrypoint for setup, daemon, desktop, and operator commands
|
|
170
|
+
- `setup.mjs` — interactive setup flow and config bootstrap
|
|
171
|
+
- `infra/` — monitor loop, recovery, lifecycle services, and runtime plumbing
|
|
172
|
+
- `workflow/` and `workflow-templates/` — workflow engine, nodes, adapters, and built-in templates
|
|
173
|
+
- `task/` — task execution, claims, archiving, and lifecycle ownership
|
|
174
|
+
- `server/` — setup server, Mini App backend, and API endpoints
|
|
175
|
+
- `ui/` — Mini App frontend assets and operator dashboard modules
|
|
176
|
+
- `telegram/` — Telegram bot, sentinel, and channel integrations
|
|
177
|
+
- `github/` and `kanban/` — GitHub auth/webhooks and Vibe-Kanban adapters
|
|
178
|
+
- `workspace/` — shared workspace registry, context indexing, and worktree lifecycle
|
|
179
|
+
- `shell/` and `agent/` — executor integrations, prompts, hooks, and fleet coordination
|
|
180
|
+
- `site/` — marketing site and generated docs website assets
|
|
181
|
+
- `docs/` and `_docs/` — product docs, deep technical references, and long-form source material
|
|
182
|
+
- `tools/` and `tests/` — build utilities, release checks, and regression coverage
|
|
181
183
|
|
|
182
184
|
---
|
|
183
185
|
|
|
184
186
|
## License
|
|
185
187
|
|
|
186
188
|
Apache-2.0
|
|
187
|
-
|
|
188
|
-
<!-- GitHub Analytics Pixel -->
|
|
189
|
-
<img src="https://cloud.umami.is/p/iR78WZdwe" alt="" width="1" height="1" style="display:none;" />
|
|
@@ -677,7 +677,7 @@ export async function promoteToGlobal(rootDir, toolId) {
|
|
|
677
677
|
* and reflect on whether to create new tools.
|
|
678
678
|
*
|
|
679
679
|
* @param {string} rootDir
|
|
680
|
-
* @param {{ limit?: number, category?: string, tags?: string[], emitReflectHint?: boolean, activeSkills?: string[], agentType?: string, template?: string, includeBuiltins?: boolean }} [opts]
|
|
680
|
+
* @param {{ limit?: number, category?: string, tags?: string[], emitReflectHint?: boolean, activeSkills?: string[], agentType?: string, template?: string, includeBuiltins?: boolean, eagerOnly?: boolean, discoveryMode?: boolean }} [opts]
|
|
681
681
|
* @returns {string}
|
|
682
682
|
*/
|
|
683
683
|
export function getToolsPromptBlock(rootDir, opts = {}) {
|
|
@@ -691,6 +691,8 @@ export function getToolsPromptBlock(rootDir, opts = {}) {
|
|
|
691
691
|
agentType,
|
|
692
692
|
template,
|
|
693
693
|
includeBuiltins = true,
|
|
694
|
+
eagerOnly = false,
|
|
695
|
+
discoveryMode = false,
|
|
694
696
|
} = opts;
|
|
695
697
|
|
|
696
698
|
let tools;
|
|
@@ -712,11 +714,25 @@ export function getToolsPromptBlock(rootDir, opts = {}) {
|
|
|
712
714
|
tools = listCustomTools(rootDir, { category, tags, includeBuiltins }).slice(0, limit);
|
|
713
715
|
}
|
|
714
716
|
|
|
717
|
+
if (eagerOnly) {
|
|
718
|
+
tools = tools.filter((tool) => {
|
|
719
|
+
if (tool.autoInject) return true;
|
|
720
|
+
if (activeSkills?.length > 0 && tool.skills?.length > 0) {
|
|
721
|
+
return activeSkills.some((skill) => tool.skills.includes(skill));
|
|
722
|
+
}
|
|
723
|
+
return false;
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
715
727
|
const lines = [
|
|
716
728
|
"## Custom Tools Library",
|
|
717
729
|
"",
|
|
718
|
-
|
|
719
|
-
|
|
730
|
+
discoveryMode
|
|
731
|
+
? "Only eagerly-loaded tools are listed below. Use the MCP discovery tools to find the rest at runtime."
|
|
732
|
+
: "The following reusable helper scripts are available. Run them via",
|
|
733
|
+
discoveryMode
|
|
734
|
+
? "Use `search`, then `get_schema`, then `execute` for tools not listed here. Use `call_discovered_tool` only for simple direct calls."
|
|
735
|
+
: "`node <tool>.mjs`, `bash <tool>.sh`, or `python3 <tool>.py`.",
|
|
720
736
|
"Built-in tools live in `bosun/tools/`; workspace tools in `.bosun/tools/`.",
|
|
721
737
|
"",
|
|
722
738
|
];
|
package/agent/agent-pool.mjs
CHANGED
|
@@ -2228,16 +2228,25 @@ export async function launchEphemeralThread(
|
|
|
2228
2228
|
if (mcpCfg.enabled !== false) {
|
|
2229
2229
|
const requestedIds = launchExtra.mcpServers || [];
|
|
2230
2230
|
const defaultIds = mcpCfg.defaultServers || [];
|
|
2231
|
+
const registry = await getMcpRegistry();
|
|
2232
|
+
let resolved = [];
|
|
2231
2233
|
if (requestedIds.length || defaultIds.length) {
|
|
2232
|
-
|
|
2233
|
-
const resolved = await registry.resolveMcpServersForAgent(
|
|
2234
|
+
resolved = await registry.resolveMcpServersForAgent(
|
|
2234
2235
|
cwd,
|
|
2235
2236
|
requestedIds,
|
|
2236
2237
|
{ defaultServers: defaultIds, catalogOverrides: mcpCfg.catalogOverrides || {} },
|
|
2237
2238
|
);
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2239
|
+
}
|
|
2240
|
+
if (typeof registry.wrapServersWithDiscoveryProxy === "function") {
|
|
2241
|
+
resolved = registry.wrapServersWithDiscoveryProxy(cwd, resolved, {
|
|
2242
|
+
enabled: mcpCfg.useDiscoveryProxy !== false,
|
|
2243
|
+
includeCustomTools: mcpCfg.includeCustomToolsInDiscoveryProxy !== false,
|
|
2244
|
+
cacheTtlMs: mcpCfg.discoveryProxyCacheTtlMs,
|
|
2245
|
+
executeTimeoutMs: mcpCfg.discoveryProxyExecuteTimeoutMs,
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
if (resolved.length) {
|
|
2249
|
+
launchExtra._resolvedMcpServers = resolved;
|
|
2241
2250
|
}
|
|
2242
2251
|
}
|
|
2243
2252
|
launchExtra._mcpResolved = true;
|
package/agent/agent-prompts.mjs
CHANGED
|
@@ -770,7 +770,7 @@ CLI:
|
|
|
770
770
|
bosun task delete <id>
|
|
771
771
|
bosun task stats [--json]
|
|
772
772
|
bosun task import <file.json>
|
|
773
|
-
|
|
773
|
+
Planner workflow: POST /api/workflows/launch-template {"templateId":"template-task-planner"} or /plan [count] [focus]
|
|
774
774
|
|
|
775
775
|
REST API (port 18432):
|
|
776
776
|
GET /api/tasks[?status=todo]
|
|
@@ -868,7 +868,7 @@ to the Bosun workspace, task board, coding agents, and system operations.
|
|
|
868
868
|
You can do everything Bosun can — through voice. This includes:
|
|
869
869
|
- **Task management**: List, create, update, delete, search, and comment on tasks
|
|
870
870
|
- **Agent delegation**: Send work to coding agents (Codex, Copilot, Claude, Gemini, OpenCode)
|
|
871
|
-
- **Agent steering**: Use /ask (read-only), /agent (code changes), or /plan (
|
|
871
|
+
- **Agent steering**: Use /ask (read-only), /agent (code changes), or /plan (run task planner workflow)
|
|
872
872
|
- **System monitoring**: Check fleet status, agent health, system configuration
|
|
873
873
|
- **Workspace navigation**: Read files, list directories, search code
|
|
874
874
|
- **Workflow management**: List and inspect workflow templates
|
package/agent/bosun-skills.mjs
CHANGED
|
@@ -62,6 +62,7 @@ function emitSkillInvokeEvent(skillName, skillTitle, opts = {}) {
|
|
|
62
62
|
* filename – the .md file written into skills/
|
|
63
63
|
* title – short human-readable name
|
|
64
64
|
* tags – array of lowercase tags agents use to match skills to tasks
|
|
65
|
+
* important – eagerly inline this skill into agent context when matched
|
|
65
66
|
* scope – "global" | "bosun" (bosun-specific internals)
|
|
66
67
|
* content – the full Markdown skill text
|
|
67
68
|
*/
|
|
@@ -70,6 +71,7 @@ export const BUILTIN_SKILLS = [
|
|
|
70
71
|
filename: "background-task-execution.md",
|
|
71
72
|
title: "Background Task Execution",
|
|
72
73
|
tags: ["background", "task", "reliability", "heartbeat", "stall", "completion"],
|
|
74
|
+
important: true,
|
|
73
75
|
scope: "global",
|
|
74
76
|
content: `# Skill: Background Task Execution
|
|
75
77
|
|
|
@@ -147,6 +149,7 @@ Your worktree path is provided via \`BOSUN_WORKTREE_PATH\`. Stay inside it.
|
|
|
147
149
|
filename: "pr-workflow.md",
|
|
148
150
|
title: "Pull Request Workflow",
|
|
149
151
|
tags: ["pr", "pull-request", "github", "review", "ci", "merge"],
|
|
152
|
+
important: true,
|
|
150
153
|
scope: "global",
|
|
151
154
|
content: `# Skill: Pull Request Workflow
|
|
152
155
|
|
|
@@ -209,6 +212,7 @@ gh run list --limit 5 # recent workflow runs
|
|
|
209
212
|
filename: "error-recovery.md",
|
|
210
213
|
title: "Error Recovery Patterns",
|
|
211
214
|
tags: ["error", "recovery", "retry", "debug", "failure"],
|
|
215
|
+
important: true,
|
|
212
216
|
scope: "global",
|
|
213
217
|
content: `# Skill: Error Recovery Patterns
|
|
214
218
|
|
|
@@ -1079,6 +1083,35 @@ export function buildSkillsIndex(skillsDir) {
|
|
|
1079
1083
|
/* directory may not exist yet */
|
|
1080
1084
|
}
|
|
1081
1085
|
|
|
1086
|
+
function extractSkillFileMetadata(filePath) {
|
|
1087
|
+
let content = "";
|
|
1088
|
+
try {
|
|
1089
|
+
content = readFileSync(filePath, "utf8");
|
|
1090
|
+
} catch {
|
|
1091
|
+
return { title: "", tags: [], important: false };
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
let title = "";
|
|
1095
|
+
let tags = [];
|
|
1096
|
+
let important = false;
|
|
1097
|
+
|
|
1098
|
+
const tagMatch = /<!--\s*tags:\s*(.+?)\s*-->/i.exec(content);
|
|
1099
|
+
if (tagMatch) {
|
|
1100
|
+
tags = tagMatch[1].split(/[,\s]+/).map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const importantMatch = /<!--\s*(?:important|eager)\s*:\s*(true|false|yes|no|on|off|1|0)\s*-->/i.exec(content);
|
|
1104
|
+
if (importantMatch) {
|
|
1105
|
+
const raw = String(importantMatch[1] || "").trim().toLowerCase();
|
|
1106
|
+
important = raw === "true" || raw === "yes" || raw === "on" || raw === "1";
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const h1 = /^#\s+(?:Skill: )?(.+)/m.exec(content);
|
|
1110
|
+
if (h1) title = h1[1].trim();
|
|
1111
|
+
|
|
1112
|
+
return { title, tags, important };
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1082
1115
|
for (const filename of files.toSorted((a, b) => a.localeCompare(b))) {
|
|
1083
1116
|
const filePath = resolve(skillsDir, filename);
|
|
1084
1117
|
let stat;
|
|
@@ -1087,31 +1120,26 @@ export function buildSkillsIndex(skillsDir) {
|
|
|
1087
1120
|
const builtin = builtinByFilename[filename];
|
|
1088
1121
|
let title = basename(filename, ".md").replaceAll("-", " ").replaceAll(/\b\w/g, (c) => c.toUpperCase());
|
|
1089
1122
|
let tags = [];
|
|
1123
|
+
let important = false;
|
|
1090
1124
|
let scope = "global";
|
|
1091
1125
|
|
|
1092
1126
|
if (builtin) {
|
|
1093
1127
|
title = builtin.title;
|
|
1094
1128
|
tags = builtin.tags;
|
|
1129
|
+
important = builtin.important === true;
|
|
1095
1130
|
scope = builtin.scope;
|
|
1096
1131
|
} else {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const tagMatch = /<!--\s*tags:\s*(.+?)\s*-->/i.exec(content);
|
|
1102
|
-
if (tagMatch) {
|
|
1103
|
-
tags = tagMatch[1].split(/[,\s]+/).map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
1104
|
-
} else {
|
|
1105
|
-
const h1 = /^#\s+(?:Skill: )?(.+)/m.exec(content);
|
|
1106
|
-
if (h1) title = h1[1].trim();
|
|
1107
|
-
}
|
|
1108
|
-
} catch { /* ignore read errors */ }
|
|
1132
|
+
const metadata = extractSkillFileMetadata(filePath);
|
|
1133
|
+
if (metadata.title) title = metadata.title;
|
|
1134
|
+
tags = metadata.tags;
|
|
1135
|
+
important = metadata.important;
|
|
1109
1136
|
}
|
|
1110
1137
|
|
|
1111
1138
|
entries.push({
|
|
1112
1139
|
filename,
|
|
1113
1140
|
title,
|
|
1114
1141
|
tags,
|
|
1142
|
+
important,
|
|
1115
1143
|
scope,
|
|
1116
1144
|
updatedAt: stat.mtime.toISOString(),
|
|
1117
1145
|
});
|
|
@@ -1212,12 +1240,12 @@ export function findRelevantSkills(bosunHome, taskTitle, taskDescription = "", o
|
|
|
1212
1240
|
.filter(({ tags }) =>
|
|
1213
1241
|
tags.some((tag) => searchText.includes(tag)),
|
|
1214
1242
|
)
|
|
1215
|
-
.map(({ filename, title, tags }) => {
|
|
1243
|
+
.map(({ filename, title, tags, important }) => {
|
|
1216
1244
|
let content = "";
|
|
1217
1245
|
try {
|
|
1218
1246
|
content = readFileSync(resolve(skillsDir, filename), "utf8");
|
|
1219
1247
|
} catch { /* skip unreadable files */ }
|
|
1220
|
-
return { filename, title, tags, content };
|
|
1248
|
+
return { filename, title, tags, important: important === true, content };
|
|
1221
1249
|
})
|
|
1222
1250
|
.filter(({ content }) => !!content);
|
|
1223
1251
|
|
|
@@ -1229,3 +1257,44 @@ export function findRelevantSkills(bosunHome, taskTitle, taskDescription = "", o
|
|
|
1229
1257
|
|
|
1230
1258
|
return matched;
|
|
1231
1259
|
}
|
|
1260
|
+
|
|
1261
|
+
export function buildRelevantSkillsPromptBlock(bosunHome, taskTitle, taskDescription = "", opts = {}) {
|
|
1262
|
+
const {
|
|
1263
|
+
maxListed = 8,
|
|
1264
|
+
includeMatchedSummary = true,
|
|
1265
|
+
} = opts;
|
|
1266
|
+
const matched = findRelevantSkills(bosunHome, taskTitle, taskDescription, opts);
|
|
1267
|
+
if (!matched.length) return "";
|
|
1268
|
+
|
|
1269
|
+
const importantMatches = matched.filter((skill) => skill.important);
|
|
1270
|
+
const summaryMatches = matched.slice(0, Math.max(1, maxListed));
|
|
1271
|
+
const lines = ["## Skills Context", ""];
|
|
1272
|
+
|
|
1273
|
+
if (importantMatches.length > 0) {
|
|
1274
|
+
lines.push(
|
|
1275
|
+
"These matched skills are marked important, so their contents are loaded directly below.",
|
|
1276
|
+
"",
|
|
1277
|
+
);
|
|
1278
|
+
for (const skill of importantMatches) {
|
|
1279
|
+
lines.push(`### Skill: ${skill.title} (\`${skill.filename}\`)`);
|
|
1280
|
+
lines.push(skill.content.trim());
|
|
1281
|
+
lines.push("");
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
if (includeMatchedSummary) {
|
|
1286
|
+
lines.push("Matched skill files:");
|
|
1287
|
+
for (const skill of summaryMatches) {
|
|
1288
|
+
const tags = Array.isArray(skill.tags) && skill.tags.length > 0
|
|
1289
|
+
? ` — tags: ${skill.tags.join(", ")}`
|
|
1290
|
+
: "";
|
|
1291
|
+
const importantLabel = skill.important ? " [important]" : "";
|
|
1292
|
+
lines.push(`- \`${skill.filename}\` — ${skill.title}${importantLabel}${tags}`);
|
|
1293
|
+
}
|
|
1294
|
+
lines.push("");
|
|
1295
|
+
lines.push("Load non-important matched skills on demand if you need their details.");
|
|
1296
|
+
lines.push("");
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return lines.join("\n").trim();
|
|
1300
|
+
}
|
package/agent/primary-agent.mjs
CHANGED
|
@@ -158,6 +158,59 @@ function appendAttachmentsToPrompt(message, attachments) {
|
|
|
158
158
|
return { message: `${message}${lines.join("\n")}`, appended: true };
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
function summarizeContextCompressionItems(items) {
|
|
162
|
+
if (!Array.isArray(items) || items.length === 0) return null;
|
|
163
|
+
|
|
164
|
+
const counts = {
|
|
165
|
+
agent: 0,
|
|
166
|
+
user: 0,
|
|
167
|
+
tool: 0,
|
|
168
|
+
other: 0,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
for (const item of items) {
|
|
172
|
+
if (!item || typeof item !== "object") continue;
|
|
173
|
+
const compressedTag = String(item._compressed || "").trim().toLowerCase();
|
|
174
|
+
const text = String(item.text || item.output || item.aggregated_output || "").toLowerCase();
|
|
175
|
+
const hasToolPlaceholder =
|
|
176
|
+
Boolean(item._cachedLogId)
|
|
177
|
+
|| text.includes("full output: bosun --tool-log")
|
|
178
|
+
|| text.includes(" chars compressed");
|
|
179
|
+
|
|
180
|
+
if (compressedTag.startsWith("agent_")) {
|
|
181
|
+
counts.agent += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (compressedTag === "user_breadcrumb") {
|
|
185
|
+
counts.user += 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (hasToolPlaceholder) {
|
|
189
|
+
counts.tool += 1;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (compressedTag) counts.other += 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const total = counts.agent + counts.user + counts.tool + counts.other;
|
|
196
|
+
if (total === 0) return null;
|
|
197
|
+
|
|
198
|
+
const detailParts = [];
|
|
199
|
+
if (counts.agent) detailParts.push(`${counts.agent} agent message${counts.agent === 1 ? "" : "s"}`);
|
|
200
|
+
if (counts.user) detailParts.push(`${counts.user} user prompt${counts.user === 1 ? "" : "s"}`);
|
|
201
|
+
if (counts.tool) detailParts.push(`${counts.tool} tool output${counts.tool === 1 ? "" : "s"}`);
|
|
202
|
+
if (counts.other) detailParts.push(`${counts.other} other item${counts.other === 1 ? "" : "s"}`);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
total,
|
|
206
|
+
counts,
|
|
207
|
+
detail: detailParts.join(", "),
|
|
208
|
+
content:
|
|
209
|
+
`Context summarized for continuation: ${total} older item${total === 1 ? "" : "s"} compressed (${detailParts.join(", ")}). ` +
|
|
210
|
+
`Session history in this view is unchanged.`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
161
214
|
function buildPrimaryToolCapabilityContract(options = {}) {
|
|
162
215
|
let rootDir = "";
|
|
163
216
|
try {
|
|
@@ -795,6 +848,18 @@ export async function execPrimaryPrompt(userMessage, options = {}) {
|
|
|
795
848
|
timestamp: new Date().toISOString(),
|
|
796
849
|
_sessionType: sessionType,
|
|
797
850
|
});
|
|
851
|
+
const compressionSummary = summarizeContextCompressionItems(pooled?.items);
|
|
852
|
+
if (compressionSummary) {
|
|
853
|
+
tracker.recordEvent(sessionId, {
|
|
854
|
+
role: "system",
|
|
855
|
+
type: "system",
|
|
856
|
+
content: compressionSummary.content,
|
|
857
|
+
timestamp: new Date().toISOString(),
|
|
858
|
+
meta: {
|
|
859
|
+
contextCompression: compressionSummary,
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
}
|
|
798
863
|
return pooled;
|
|
799
864
|
}
|
|
800
865
|
|
|
@@ -872,6 +937,18 @@ export async function execPrimaryPrompt(userMessage, options = {}) {
|
|
|
872
937
|
timestamp: new Date().toISOString(),
|
|
873
938
|
_sessionType: sessionType,
|
|
874
939
|
});
|
|
940
|
+
const compressionSummary = summarizeContextCompressionItems(result?.items);
|
|
941
|
+
if (compressionSummary) {
|
|
942
|
+
tracker.recordEvent(sessionId, {
|
|
943
|
+
role: "system",
|
|
944
|
+
type: "system",
|
|
945
|
+
content: compressionSummary.content,
|
|
946
|
+
timestamp: new Date().toISOString(),
|
|
947
|
+
meta: {
|
|
948
|
+
contextCompression: compressionSummary,
|
|
949
|
+
},
|
|
950
|
+
});
|
|
951
|
+
}
|
|
875
952
|
}
|
|
876
953
|
return result;
|
|
877
954
|
} catch (err) {
|
package/bosun.schema.json
CHANGED
|
@@ -1049,6 +1049,28 @@
|
|
|
1049
1049
|
},
|
|
1050
1050
|
"description": "Environment variable overrides keyed by MCP server ID"
|
|
1051
1051
|
},
|
|
1052
|
+
"useDiscoveryProxy": {
|
|
1053
|
+
"type": "boolean",
|
|
1054
|
+
"default": true,
|
|
1055
|
+
"description": "Wrap resolved MCP servers behind Bosun's compact search/schema/call discovery proxy to reduce tool-definition prompt overhead."
|
|
1056
|
+
},
|
|
1057
|
+
"includeCustomToolsInDiscoveryProxy": {
|
|
1058
|
+
"type": "boolean",
|
|
1059
|
+
"default": true,
|
|
1060
|
+
"description": "Expose Bosun custom tools through the discovery proxy alongside external MCP servers."
|
|
1061
|
+
},
|
|
1062
|
+
"discoveryProxyCacheTtlMs": {
|
|
1063
|
+
"type": "integer",
|
|
1064
|
+
"minimum": 1000,
|
|
1065
|
+
"default": 60000,
|
|
1066
|
+
"description": "How long Bosun caches wrapped MCP tool catalogs inside the discovery proxy before refreshing them."
|
|
1067
|
+
},
|
|
1068
|
+
"discoveryProxyExecuteTimeoutMs": {
|
|
1069
|
+
"type": "integer",
|
|
1070
|
+
"minimum": 1000,
|
|
1071
|
+
"default": 10000,
|
|
1072
|
+
"description": "Default timeout for the discovery proxy execute tool, which runs short orchestration code against discovered tools."
|
|
1073
|
+
},
|
|
1052
1074
|
"autoInstallDefaults": {
|
|
1053
1075
|
"type": "boolean",
|
|
1054
1076
|
"default": true,
|