bosun 0.41.0 → 0.41.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.
- package/.env.example +8 -0
- package/README.md +20 -0
- package/agent/agent-event-bus.mjs +248 -6
- package/agent/agent-pool.mjs +125 -28
- package/agent/agent-work-analyzer.mjs +8 -16
- package/agent/retry-queue.mjs +164 -0
- package/bosun.config.example.json +25 -0
- package/bosun.schema.json +825 -183
- package/cli.mjs +59 -5
- package/config/config.mjs +130 -3
- package/infra/monitor.mjs +693 -67
- package/infra/runtime-accumulator.mjs +376 -84
- package/infra/session-tracker.mjs +82 -25
- package/lib/codebase-audit.mjs +133 -18
- package/package.json +23 -4
- package/server/setup-web-server.mjs +25 -0
- package/server/ui-server.mjs +248 -29
- package/setup.mjs +27 -24
- package/shell/codex-shell.mjs +34 -3
- package/shell/copilot-shell.mjs +50 -8
- package/task/msg-hub.mjs +193 -0
- package/task/pipeline.mjs +544 -0
- package/task/task-cli.mjs +38 -2
- package/task/task-executor-pipeline.mjs +143 -0
- package/task/task-executor.mjs +36 -27
- package/telegram/get-telegram-chat-id.mjs +57 -47
- package/ui/components/workspace-switcher.js +7 -7
- package/ui/demo-defaults.js +15694 -10573
- package/ui/modules/settings-schema.js +2 -0
- package/ui/modules/state.js +54 -57
- package/ui/modules/voice-client-sdk.js +375 -36
- package/ui/modules/voice-client.js +140 -31
- package/ui/setup.html +68 -2
- package/ui/styles/components.css +57 -0
- package/ui/styles.css +201 -1
- package/ui/tabs/dashboard.js +74 -0
- package/ui/tabs/logs.js +10 -0
- package/ui/tabs/settings.js +178 -99
- package/ui/tabs/tasks.js +31 -1
- package/ui/tabs/telemetry.js +34 -0
- package/ui/tabs/workflow-canvas-utils.mjs +8 -1
- package/ui/tabs/workflows.js +532 -275
- package/voice/voice-agents-sdk.mjs +1 -1
- package/voice/voice-relay.mjs +6 -6
- package/workflow/declarative-workflows.mjs +145 -0
- package/workflow/msg-hub.mjs +237 -0
- package/workflow/pipeline-workflows.mjs +287 -0
- package/workflow/pipeline.mjs +828 -315
- package/workflow/workflow-cli.mjs +128 -0
- package/workflow/workflow-engine.mjs +329 -17
- package/workflow/workflow-nodes/custom-loader.mjs +250 -0
- package/workflow/workflow-nodes.mjs +1955 -223
- package/workflow/workflow-templates.mjs +26 -8
- package/workflow-templates/agents.mjs +0 -1
- package/workflow-templates/bosun-native.mjs +212 -2
- package/workflow-templates/continuation-loop.mjs +339 -0
- package/workflow-templates/github.mjs +516 -40
- package/workflow-templates/planning.mjs +446 -17
- package/workflow-templates/reliability.mjs +65 -12
- package/workflow-templates/task-batch.mjs +24 -8
- package/workflow-templates/task-lifecycle.mjs +83 -6
- package/workspace/context-cache.mjs +66 -18
- package/workspace/workspace-manager.mjs +2 -1
- package/workflow-templates/issue-continuation.mjs +0 -243
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
PR_CONFLICT_RESOLVER_TEMPLATE,
|
|
46
46
|
STALE_PR_REAPER_TEMPLATE,
|
|
47
47
|
RELEASE_DRAFTER_TEMPLATE,
|
|
48
|
+
BOSUN_PR_PROGRESSOR_TEMPLATE,
|
|
48
49
|
BOSUN_PR_WATCHDOG_TEMPLATE,
|
|
49
50
|
GITHUB_KANBAN_SYNC_TEMPLATE,
|
|
50
51
|
SDK_CONFLICT_RESOLVER_TEMPLATE,
|
|
@@ -139,6 +140,11 @@ import {
|
|
|
139
140
|
FLOW_CONTROL_SUITE_TEMPLATE,
|
|
140
141
|
} from "../workflow-templates/coverage.mjs";
|
|
141
142
|
|
|
143
|
+
// Continuation Loop (issue-state continuation polling)
|
|
144
|
+
import {
|
|
145
|
+
CONTINUATION_LOOP_TEMPLATE,
|
|
146
|
+
} from "../workflow-templates/continuation-loop.mjs";
|
|
147
|
+
|
|
142
148
|
// MCP Integration (MCP tool → workflow data piping)
|
|
143
149
|
import {
|
|
144
150
|
MCP_TOOL_CHAIN_TEMPLATE,
|
|
@@ -151,6 +157,7 @@ import {
|
|
|
151
157
|
import {
|
|
152
158
|
BOSUN_TOOL_PIPELINE_TEMPLATE,
|
|
153
159
|
WORKFLOW_COMPOSITION_TEMPLATE,
|
|
160
|
+
INLINE_WORKFLOW_COMPOSITION_TEMPLATE,
|
|
154
161
|
MCP_TO_BOSUN_BRIDGE_TEMPLATE,
|
|
155
162
|
GIT_HEALTH_PIPELINE_TEMPLATE,
|
|
156
163
|
} from "../workflow-templates/bosun-native.mjs";
|
|
@@ -163,6 +170,7 @@ export {
|
|
|
163
170
|
PR_CONFLICT_RESOLVER_TEMPLATE,
|
|
164
171
|
STALE_PR_REAPER_TEMPLATE,
|
|
165
172
|
RELEASE_DRAFTER_TEMPLATE,
|
|
173
|
+
BOSUN_PR_PROGRESSOR_TEMPLATE,
|
|
166
174
|
BOSUN_PR_WATCHDOG_TEMPLATE,
|
|
167
175
|
GITHUB_KANBAN_SYNC_TEMPLATE,
|
|
168
176
|
SDK_CONFLICT_RESOLVER_TEMPLATE,
|
|
@@ -211,12 +219,14 @@ export {
|
|
|
211
219
|
MCP_RESEARCH_PROBE_TEMPLATE,
|
|
212
220
|
AGENT_EXECUTION_PIPELINE_TEMPLATE,
|
|
213
221
|
FLOW_CONTROL_SUITE_TEMPLATE,
|
|
222
|
+
CONTINUATION_LOOP_TEMPLATE,
|
|
214
223
|
MCP_TOOL_CHAIN_TEMPLATE,
|
|
215
224
|
MCP_GITHUB_PR_MONITOR_TEMPLATE,
|
|
216
225
|
MCP_CROSS_SERVER_PIPELINE_TEMPLATE,
|
|
217
226
|
MCP_ITERATIVE_RESEARCH_TEMPLATE,
|
|
218
227
|
BOSUN_TOOL_PIPELINE_TEMPLATE,
|
|
219
228
|
WORKFLOW_COMPOSITION_TEMPLATE,
|
|
229
|
+
INLINE_WORKFLOW_COMPOSITION_TEMPLATE,
|
|
220
230
|
MCP_TO_BOSUN_BRIDGE_TEMPLATE,
|
|
221
231
|
GIT_HEALTH_PIPELINE_TEMPLATE,
|
|
222
232
|
};
|
|
@@ -249,6 +259,7 @@ export const WORKFLOW_TEMPLATES = Object.freeze([
|
|
|
249
259
|
PR_CONFLICT_RESOLVER_TEMPLATE,
|
|
250
260
|
STALE_PR_REAPER_TEMPLATE,
|
|
251
261
|
RELEASE_DRAFTER_TEMPLATE,
|
|
262
|
+
BOSUN_PR_PROGRESSOR_TEMPLATE,
|
|
252
263
|
BOSUN_PR_WATCHDOG_TEMPLATE,
|
|
253
264
|
GITHUB_KANBAN_SYNC_TEMPLATE,
|
|
254
265
|
SDK_CONFLICT_RESOLVER_TEMPLATE,
|
|
@@ -308,6 +319,8 @@ export const WORKFLOW_TEMPLATES = Object.freeze([
|
|
|
308
319
|
MCP_RESEARCH_PROBE_TEMPLATE,
|
|
309
320
|
AGENT_EXECUTION_PIPELINE_TEMPLATE,
|
|
310
321
|
FLOW_CONTROL_SUITE_TEMPLATE,
|
|
322
|
+
// ── Continuation Loop ──
|
|
323
|
+
CONTINUATION_LOOP_TEMPLATE,
|
|
311
324
|
// ── MCP Integration (MCP tool → workflow data piping) ──
|
|
312
325
|
MCP_TOOL_CHAIN_TEMPLATE,
|
|
313
326
|
MCP_GITHUB_PR_MONITOR_TEMPLATE,
|
|
@@ -316,6 +329,7 @@ export const WORKFLOW_TEMPLATES = Object.freeze([
|
|
|
316
329
|
// ── Bosun Native (tools, sub-workflows, functions) ──
|
|
317
330
|
BOSUN_TOOL_PIPELINE_TEMPLATE,
|
|
318
331
|
WORKFLOW_COMPOSITION_TEMPLATE,
|
|
332
|
+
INLINE_WORKFLOW_COMPOSITION_TEMPLATE,
|
|
319
333
|
MCP_TO_BOSUN_BRIDGE_TEMPLATE,
|
|
320
334
|
GIT_HEALTH_PIPELINE_TEMPLATE,
|
|
321
335
|
]);
|
|
@@ -499,7 +513,8 @@ export function reconcileInstalledTemplates(engine, opts = {}) {
|
|
|
499
513
|
result.scanned += 1;
|
|
500
514
|
|
|
501
515
|
try {
|
|
502
|
-
const
|
|
516
|
+
const previousState = def.metadata?.templateState || null;
|
|
517
|
+
const before = stableStringify(previousState);
|
|
503
518
|
applyWorkflowTemplateState(def);
|
|
504
519
|
const state = def.metadata?.templateState || null;
|
|
505
520
|
const after = stableStringify(state);
|
|
@@ -526,9 +541,8 @@ export function reconcileInstalledTemplates(engine, opts = {}) {
|
|
|
526
541
|
});
|
|
527
542
|
}
|
|
528
543
|
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
forceUpdateTemplateIds.has(String(state.templateId || "").trim());
|
|
544
|
+
const templateId = String(state.templateId || "").trim();
|
|
545
|
+
const shouldForceUpdate = templateId && forceUpdateTemplateIds.has(templateId);
|
|
532
546
|
if (shouldForceUpdate) {
|
|
533
547
|
const saved = updateWorkflowFromTemplate(engine, def.id, { mode: "replace", force: true });
|
|
534
548
|
result.autoUpdated += 1;
|
|
@@ -537,7 +551,8 @@ export function reconcileInstalledTemplates(engine, opts = {}) {
|
|
|
537
551
|
continue;
|
|
538
552
|
}
|
|
539
553
|
|
|
540
|
-
|
|
554
|
+
const wasCustomized = previousState?.isCustomized === true;
|
|
555
|
+
if (autoUpdateUnmodified && state.updateAvailable === true && !wasCustomized) {
|
|
541
556
|
const saved = updateWorkflowFromTemplate(engine, def.id, { mode: "replace", force: true });
|
|
542
557
|
result.autoUpdated += 1;
|
|
543
558
|
result.updatedWorkflowIds.push(saved.id);
|
|
@@ -1060,9 +1075,13 @@ export function getWorkflowSetupProfile(profileId = "balanced") {
|
|
|
1060
1075
|
* @returns {string[]}
|
|
1061
1076
|
*/
|
|
1062
1077
|
export function resolveWorkflowTemplateIds(opts = {}) {
|
|
1078
|
+
const fromWorkflowConfig = resolveWorkflowTemplateConfig(opts.workflows || []);
|
|
1063
1079
|
const explicit = normalizeTemplateIdList(opts.templateIds || []);
|
|
1064
|
-
if (explicit.length > 0)
|
|
1065
|
-
|
|
1080
|
+
if (explicit.length > 0) {
|
|
1081
|
+
return normalizeTemplateIdList([...explicit, ...fromWorkflowConfig.templateIds]);
|
|
1082
|
+
}
|
|
1083
|
+
const fromProfile = resolveProfileTemplateIds(opts.profileId || "balanced");
|
|
1084
|
+
return normalizeTemplateIdList([...fromProfile, ...fromWorkflowConfig.templateIds]);
|
|
1066
1085
|
}
|
|
1067
1086
|
|
|
1068
1087
|
function coerceTemplateVariableValue(rawValue, defaultValue) {
|
|
@@ -1256,4 +1275,3 @@ export function installRecommendedTemplates(engine, overridesById = {}) {
|
|
|
1256
1275
|
.map((template) => template.id);
|
|
1257
1276
|
return installTemplateSet(engine, recommendedIds, overridesById);
|
|
1258
1277
|
}
|
|
1259
|
-
|
|
@@ -175,7 +175,217 @@ export const WORKFLOW_COMPOSITION_TEMPLATE = {
|
|
|
175
175
|
},
|
|
176
176
|
};
|
|
177
177
|
|
|
178
|
-
// ── Template 3:
|
|
178
|
+
// ── Template 3: Inline Workflow Composition ────────────────────────────────
|
|
179
|
+
// Demonstrates: Parent workflow containing multiple embedded child workflows
|
|
180
|
+
// Pattern: Inline preflight → inline execution plan → inline summary
|
|
181
|
+
|
|
182
|
+
resetLayout();
|
|
183
|
+
export const INLINE_WORKFLOW_COMPOSITION_TEMPLATE = {
|
|
184
|
+
id: "template-inline-workflow-composition",
|
|
185
|
+
name: "Inline Workflow Composition",
|
|
186
|
+
category: "mcp-integration",
|
|
187
|
+
enabled: true,
|
|
188
|
+
trigger: "trigger.manual",
|
|
189
|
+
description:
|
|
190
|
+
"Compose a parent workflow from embedded child workflows using action.inline_workflow. " +
|
|
191
|
+
"Demonstrates bounded sequential stages that stay inside the parent workflow " +
|
|
192
|
+
"while preserving child run/context boundaries and structured output handoff.",
|
|
193
|
+
variables: {
|
|
194
|
+
inputPayload: "{\"steps\":[\"lint\",\"test\",\"build\"],\"strict\":true}",
|
|
195
|
+
defaultStageOwner: "bosun",
|
|
196
|
+
},
|
|
197
|
+
nodes: [
|
|
198
|
+
node("trigger", "trigger.manual", "Start"),
|
|
199
|
+
|
|
200
|
+
node("inline-prepare", "action.inline_workflow", "Inline Prepare", {
|
|
201
|
+
mode: "sync",
|
|
202
|
+
outputVariable: "inlinePrepareResult",
|
|
203
|
+
input: {
|
|
204
|
+
inputPayload: "{{inputPayload}}",
|
|
205
|
+
defaultStageOwner: "{{defaultStageOwner}}",
|
|
206
|
+
},
|
|
207
|
+
workflow: {
|
|
208
|
+
trigger: "trigger.workflow_call",
|
|
209
|
+
nodes: [
|
|
210
|
+
{
|
|
211
|
+
id: "trigger",
|
|
212
|
+
type: "trigger.workflow_call",
|
|
213
|
+
label: "Inline Trigger",
|
|
214
|
+
config: {
|
|
215
|
+
inputs: {
|
|
216
|
+
inputPayload: { type: "string", required: false },
|
|
217
|
+
defaultStageOwner: { type: "string", required: false },
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "normalize-payload",
|
|
223
|
+
type: "action.set_variable",
|
|
224
|
+
label: "Normalize Payload",
|
|
225
|
+
config: {
|
|
226
|
+
key: "normalizedPayload",
|
|
227
|
+
value:
|
|
228
|
+
"(() => {" +
|
|
229
|
+
"const raw = $data?.inputPayload;" +
|
|
230
|
+
"if (raw && typeof raw === 'object') return raw;" +
|
|
231
|
+
"const text = String(raw || '').trim();" +
|
|
232
|
+
"if (!text) return { steps: [] };" +
|
|
233
|
+
"try { return JSON.parse(text); } catch { return { steps: [text] }; }" +
|
|
234
|
+
"})()",
|
|
235
|
+
isExpression: true,
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: "finish",
|
|
240
|
+
type: "flow.end",
|
|
241
|
+
label: "Finish Prepare",
|
|
242
|
+
config: {
|
|
243
|
+
status: "completed",
|
|
244
|
+
output: {
|
|
245
|
+
stageOwner: "{{defaultStageOwner}}",
|
|
246
|
+
normalizedPayload: "{{$data?.normalizedPayload || { steps: [] }}}",
|
|
247
|
+
stepCount:
|
|
248
|
+
"{{$data?.normalizedPayload && Array.isArray($data.normalizedPayload.steps) ? $data.normalizedPayload.steps.length : 0}}",
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
edges: [
|
|
254
|
+
{ id: "e1", source: "trigger", target: "normalize-payload" },
|
|
255
|
+
{ id: "e2", source: "normalize-payload", target: "finish" },
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
}),
|
|
259
|
+
|
|
260
|
+
node("inline-plan", "action.inline_workflow", "Inline Stage Plan", {
|
|
261
|
+
mode: "sync",
|
|
262
|
+
outputVariable: "inlinePlanResult",
|
|
263
|
+
input: {
|
|
264
|
+
stageOwner: "{{$ctx.getNodeOutput('inline-prepare')?.stageOwner || $data?.defaultStageOwner || 'bosun'}}",
|
|
265
|
+
normalizedPayload: "{{$ctx.getNodeOutput('inline-prepare')?.normalizedPayload || { steps: [] }}}",
|
|
266
|
+
},
|
|
267
|
+
workflow: {
|
|
268
|
+
trigger: "trigger.workflow_call",
|
|
269
|
+
nodes: [
|
|
270
|
+
{
|
|
271
|
+
id: "trigger",
|
|
272
|
+
type: "trigger.workflow_call",
|
|
273
|
+
label: "Inline Trigger",
|
|
274
|
+
config: {
|
|
275
|
+
inputs: {
|
|
276
|
+
stageOwner: { type: "string", required: false },
|
|
277
|
+
normalizedPayload: { type: "object", required: false },
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: "build-plan",
|
|
283
|
+
type: "action.set_variable",
|
|
284
|
+
label: "Build Stage Plan",
|
|
285
|
+
config: {
|
|
286
|
+
key: "stagePlan",
|
|
287
|
+
value:
|
|
288
|
+
"(() => {" +
|
|
289
|
+
"const steps = Array.isArray($data?.normalizedPayload?.steps) ? $data.normalizedPayload.steps : [];" +
|
|
290
|
+
"return steps.map((step, index) => ({ index: index + 1, step: String(step || ''), owner: String($data?.stageOwner || 'bosun') }));" +
|
|
291
|
+
"})()",
|
|
292
|
+
isExpression: true,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
id: "finish",
|
|
297
|
+
type: "flow.end",
|
|
298
|
+
label: "Finish Plan",
|
|
299
|
+
config: {
|
|
300
|
+
status: "completed",
|
|
301
|
+
output: {
|
|
302
|
+
stagePlan: "{{$data?.stagePlan || []}}",
|
|
303
|
+
stageCount: "{{$data?.stagePlan?.length || 0}}",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
edges: [
|
|
309
|
+
{ id: "e1", source: "trigger", target: "build-plan" },
|
|
310
|
+
{ id: "e2", source: "build-plan", target: "finish" },
|
|
311
|
+
],
|
|
312
|
+
},
|
|
313
|
+
}),
|
|
314
|
+
|
|
315
|
+
node("inline-summarize", "action.inline_workflow", "Inline Summary", {
|
|
316
|
+
mode: "sync",
|
|
317
|
+
outputVariable: "inlineSummaryResult",
|
|
318
|
+
input: {
|
|
319
|
+
stagePlan: "{{$ctx.getNodeOutput('inline-plan')?.stagePlan || []}}",
|
|
320
|
+
stepCount: "{{$ctx.getNodeOutput('inline-prepare')?.stepCount || 0}}",
|
|
321
|
+
},
|
|
322
|
+
workflow: {
|
|
323
|
+
trigger: "trigger.workflow_call",
|
|
324
|
+
nodes: [
|
|
325
|
+
{
|
|
326
|
+
id: "trigger",
|
|
327
|
+
type: "trigger.workflow_call",
|
|
328
|
+
label: "Inline Trigger",
|
|
329
|
+
config: {
|
|
330
|
+
inputs: {
|
|
331
|
+
stagePlan: { type: "array", required: false },
|
|
332
|
+
stepCount: { type: "number", required: false },
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: "summarize",
|
|
338
|
+
type: "action.set_variable",
|
|
339
|
+
label: "Summarize Plan",
|
|
340
|
+
config: {
|
|
341
|
+
key: "summary",
|
|
342
|
+
value:
|
|
343
|
+
"(() => {" +
|
|
344
|
+
"const plan = Array.isArray($data?.stagePlan) ? $data.stagePlan : [];" +
|
|
345
|
+
"const labels = plan.map((entry) => String(entry.index) + ':' + String(entry.step || '')).join(', ');" +
|
|
346
|
+
"return {" +
|
|
347
|
+
" summaryMessage: 'Prepared ' + String(Number($data?.stepCount || 0)) + ' inline stage(s)' + (labels ? ' -> ' + labels : '')," +
|
|
348
|
+
" plannedSteps: plan," +
|
|
349
|
+
"};" +
|
|
350
|
+
"})()",
|
|
351
|
+
isExpression: true,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: "finish",
|
|
356
|
+
type: "flow.end",
|
|
357
|
+
label: "Finish Summary",
|
|
358
|
+
config: {
|
|
359
|
+
status: "completed",
|
|
360
|
+
output: "{{$data?.summary || { summaryMessage: 'Prepared 0 inline stage(s)', plannedSteps: [] }}}",
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
edges: [
|
|
365
|
+
{ id: "e1", source: "trigger", target: "summarize" },
|
|
366
|
+
{ id: "e2", source: "summarize", target: "finish" },
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
}),
|
|
370
|
+
|
|
371
|
+
node("log-summary", "notify.log", "Log Inline Summary", {
|
|
372
|
+
message: "{{inline-summarize.summaryMessage}}",
|
|
373
|
+
level: "info",
|
|
374
|
+
}),
|
|
375
|
+
],
|
|
376
|
+
edges: [
|
|
377
|
+
edge("trigger", "inline-prepare"),
|
|
378
|
+
edge("inline-prepare", "inline-plan"),
|
|
379
|
+
edge("inline-plan", "inline-summarize"),
|
|
380
|
+
edge("inline-summarize", "log-summary"),
|
|
381
|
+
],
|
|
382
|
+
metadata: {
|
|
383
|
+
author: "virtengine",
|
|
384
|
+
tags: ["workflow", "composition", "inline", "parent-workflow", "embedded"],
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// ── Template 4: MCP-to-Bosun Bridge ─────────────────────────────────────────
|
|
179
389
|
// Demonstrates: MCP Tool → Extract → Bosun Function → Sub-Workflow
|
|
180
390
|
// Pattern: External MCP data → Bosun internal actions → Workflow dispatch
|
|
181
391
|
|
|
@@ -277,7 +487,7 @@ export const MCP_TO_BOSUN_BRIDGE_TEMPLATE = {
|
|
|
277
487
|
},
|
|
278
488
|
};
|
|
279
489
|
|
|
280
|
-
// ── Template
|
|
490
|
+
// ── Template 5: Git Health → Tool Analysis → Sub-Workflow ───────────────────
|
|
281
491
|
// Demonstrates: Bosun functions + tools + sub-workflow in one pipeline
|
|
282
492
|
|
|
283
493
|
resetLayout();
|