bosun 0.41.8 → 0.41.10
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 +1 -1
- package/README.md +23 -1
- package/agent/agent-event-bus.mjs +31 -2
- package/agent/agent-pool.mjs +275 -40
- package/agent/agent-prompts.mjs +9 -1
- package/agent/agent-supervisor.mjs +22 -0
- package/agent/autofix.mjs +1 -1
- package/agent/primary-agent.mjs +115 -5
- package/cli.mjs +3 -2
- package/config/config.mjs +47 -33
- package/config/context-shredding-config.mjs +1 -1
- package/config/repo-root.mjs +41 -33
- package/desktop/main.mjs +350 -25
- package/desktop/preload.cjs +8 -0
- package/desktop/preload.mjs +19 -0
- package/entrypoint.mjs +332 -0
- package/git/sdk-conflict-resolver.mjs +1 -1
- package/infra/health-status.mjs +72 -0
- package/infra/library-manager.mjs +58 -1
- package/infra/maintenance.mjs +1 -2
- package/infra/monitor.mjs +26 -8
- package/infra/session-tracker.mjs +30 -3
- package/package.json +12 -4
- package/server/bosun-mcp-server.mjs +1004 -0
- package/server/setup-web-server.mjs +288 -259
- package/server/ui-server.mjs +1323 -26
- package/shell/claude-shell.mjs +14 -1
- package/shell/codex-config.mjs +1 -1
- package/shell/codex-model-profiles.mjs +170 -30
- package/shell/codex-shell.mjs +63 -18
- package/shell/opencode-providers.mjs +20 -8
- package/task/task-executor.mjs +28 -0
- package/task/task-store.mjs +13 -4
- package/telegram/telegram-sentinel.mjs +54 -3
- package/tools/list-todos.mjs +7 -1
- package/ui/app.js +3 -2
- package/ui/components/agent-selector.js +127 -0
- package/ui/components/session-list.js +15 -10
- package/ui/demo-defaults.js +334 -336
- package/ui/modules/router.js +2 -0
- package/ui/modules/state.js +13 -5
- package/ui/tabs/chat.js +3 -0
- package/ui/tabs/library.js +284 -52
- package/ui/tabs/tasks.js +5 -13
- package/ui/tabs/workflows.js +766 -3
- package/workflow/workflow-engine.mjs +246 -5
- package/workflow/workflow-nodes/definitions.mjs +37 -0
- package/workflow/workflow-nodes.mjs +1014 -184
- package/workflow/workflow-templates.mjs +0 -5
- package/workflow-templates/_helpers.mjs +253 -0
- package/workflow-templates/agents.mjs +199 -226
- package/workflow-templates/github.mjs +106 -16
- package/workflow-templates/sub-workflows.mjs +233 -0
- package/workflow-templates/task-execution.mjs +125 -471
- package/workflow-templates/task-lifecycle.mjs +11 -48
- package/workspace/command-diagnostics.mjs +460 -0
- package/workspace/context-cache.mjs +396 -28
- package/workspace/worktree-manager.mjs +1 -1
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These utilities generate positioned nodes and edges in a consistent format.
|
|
5
5
|
* All template module files import from here.
|
|
6
|
+
*
|
|
7
|
+
* ## Sub-Workflow Composition
|
|
8
|
+
*
|
|
9
|
+
* Templates can be composed from reusable sub-workflows using:
|
|
10
|
+
*
|
|
11
|
+
* subWorkflow(id, nodes, edges) — define a reusable node+edge fragment
|
|
12
|
+
* embedSubWorkflow(sub, prefix, overrides) — embed a fragment into a parent
|
|
13
|
+
* agentPhase(id, label, prompt, extra) — shorthand for action.run_agent node
|
|
14
|
+
* makeAgentPipeline(opts) — factory for trigger → phase₁ → … → done
|
|
15
|
+
*
|
|
16
|
+
* Sub-workflows use prefixed node IDs to avoid collisions when embedded
|
|
17
|
+
* multiple times in the same parent workflow.
|
|
6
18
|
*/
|
|
7
19
|
|
|
8
20
|
// ── Layout state ────────────────────────────────────────────────────────────
|
|
@@ -217,3 +229,244 @@ export function normalizeTemplateLayoutInPlace(template, opts = {}) {
|
|
|
217
229
|
|
|
218
230
|
return template;
|
|
219
231
|
}
|
|
232
|
+
|
|
233
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
234
|
+
// Sub-Workflow Composition Primitives
|
|
235
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Standard boilerplate config for action.run_agent nodes.
|
|
239
|
+
* Templates merge this with a `prompt` to avoid repeating 10 identical fields.
|
|
240
|
+
* @param {object} [extra] - Optional overrides (e.g. resolveMode, failOnError)
|
|
241
|
+
* @returns {object} Config object with template variables
|
|
242
|
+
*/
|
|
243
|
+
export function agentDefaults(extra = {}) {
|
|
244
|
+
return {
|
|
245
|
+
taskId: "{{taskId}}",
|
|
246
|
+
sdk: "{{resolvedSdk}}",
|
|
247
|
+
model: "{{resolvedModel}}",
|
|
248
|
+
agentProfile: "{{agentProfile}}",
|
|
249
|
+
cwd: "{{worktreePath}}",
|
|
250
|
+
timeoutMs: "{{taskTimeoutMs}}",
|
|
251
|
+
maxRetries: "{{maxRetries}}",
|
|
252
|
+
maxContinues: "{{maxContinues}}",
|
|
253
|
+
resolveMode: "library",
|
|
254
|
+
failOnError: false,
|
|
255
|
+
...extra,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Create an action.run_agent node with standard boilerplate merged in.
|
|
261
|
+
* Only the prompt (and optional extra config) needs to be specified.
|
|
262
|
+
* @param {string} id - Node ID
|
|
263
|
+
* @param {string} label - Display label
|
|
264
|
+
* @param {string} prompt - The agent prompt for this phase
|
|
265
|
+
* @param {object} [extra] - Additional config overrides
|
|
266
|
+
* @param {object} [opts] - Position / output overrides
|
|
267
|
+
* @returns {object} Node definition
|
|
268
|
+
*/
|
|
269
|
+
export function agentPhase(id, label, prompt, extra = {}, opts = {}) {
|
|
270
|
+
return node(id, "action.run_agent", label, {
|
|
271
|
+
prompt,
|
|
272
|
+
...agentDefaults(extra),
|
|
273
|
+
}, opts);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Define a reusable sub-workflow fragment (a group of nodes + edges).
|
|
278
|
+
*
|
|
279
|
+
* A sub-workflow is not a standalone workflow — it's a composable fragment
|
|
280
|
+
* that can be embedded into parent workflows via embedSubWorkflow().
|
|
281
|
+
*
|
|
282
|
+
* @param {string} id - Unique identifier for this sub-workflow definition
|
|
283
|
+
* @param {Array} nodes - Array of node definitions (from node() / agentPhase())
|
|
284
|
+
* @param {Array} edges - Array of edge definitions (from edge())
|
|
285
|
+
* @param {object} [meta] - Optional metadata (description, entryNode, exitNode)
|
|
286
|
+
* @returns {object} Sub-workflow definition { id, nodes, edges, meta }
|
|
287
|
+
*/
|
|
288
|
+
export function subWorkflow(id, nodes, edges, meta = {}) {
|
|
289
|
+
if (!id || typeof id !== "string") {
|
|
290
|
+
throw new Error("subWorkflow: id must be a non-empty string");
|
|
291
|
+
}
|
|
292
|
+
if (!Array.isArray(nodes) || nodes.length === 0) {
|
|
293
|
+
throw new Error(`subWorkflow(${id}): must have at least one node`);
|
|
294
|
+
}
|
|
295
|
+
if (!Array.isArray(edges)) {
|
|
296
|
+
throw new Error(`subWorkflow(${id}): edges must be an array`);
|
|
297
|
+
}
|
|
298
|
+
// Validate that edge sources/targets reference defined nodes
|
|
299
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
300
|
+
for (const e of edges) {
|
|
301
|
+
if (!nodeIds.has(e.source)) {
|
|
302
|
+
throw new Error(`subWorkflow(${id}): edge source "${e.source}" not found in nodes`);
|
|
303
|
+
}
|
|
304
|
+
if (!nodeIds.has(e.target)) {
|
|
305
|
+
throw new Error(`subWorkflow(${id}): edge target "${e.target}" not found in nodes`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
id,
|
|
310
|
+
nodes,
|
|
311
|
+
edges,
|
|
312
|
+
meta: {
|
|
313
|
+
entryNode: meta.entryNode || nodes[0].id,
|
|
314
|
+
exitNode: meta.exitNode || nodes[nodes.length - 1].id,
|
|
315
|
+
description: meta.description || "",
|
|
316
|
+
...meta,
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Embed a sub-workflow into a parent workflow by prefixing all node/edge IDs.
|
|
323
|
+
*
|
|
324
|
+
* This is the core composition mechanism — like calling a function.
|
|
325
|
+
* Each embed gets a unique prefix so the same sub-workflow can appear
|
|
326
|
+
* multiple times in one parent without ID collisions.
|
|
327
|
+
*
|
|
328
|
+
* Returns { nodes, edges, entryNodeId, exitNodeId } ready to spread
|
|
329
|
+
* into a parent template's nodes/edges arrays and wire with edges.
|
|
330
|
+
*
|
|
331
|
+
* @param {object} sub - Sub-workflow from subWorkflow()
|
|
332
|
+
* @param {string} prefix - Unique prefix for this embed (e.g. "backend-")
|
|
333
|
+
* @param {object} [opts] - Embedding options
|
|
334
|
+
* @param {object} [opts.configOverrides] - Config values to merge into every node
|
|
335
|
+
* @param {object} [opts.nodeOverrides] - Per-node config patches keyed by ORIGINAL (unprefixed) node ID
|
|
336
|
+
* @returns {{ nodes: Array, edges: Array, entryNodeId: string, exitNodeId: string }}
|
|
337
|
+
*/
|
|
338
|
+
export function embedSubWorkflow(sub, prefix, opts = {}) {
|
|
339
|
+
if (!sub || !Array.isArray(sub.nodes)) {
|
|
340
|
+
throw new Error("embedSubWorkflow: first argument must be a sub-workflow");
|
|
341
|
+
}
|
|
342
|
+
if (!prefix || typeof prefix !== "string") {
|
|
343
|
+
throw new Error("embedSubWorkflow: prefix must be a non-empty string");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Support legacy positional: embedSubWorkflow(sub, prefix, configOverrides)
|
|
347
|
+
const options = (opts && typeof opts === "object" && !Array.isArray(opts))
|
|
348
|
+
? (opts.configOverrides || opts.nodeOverrides ? opts : { configOverrides: opts })
|
|
349
|
+
: {};
|
|
350
|
+
const configOverrides = options.configOverrides || {};
|
|
351
|
+
const nodeOverrides = options.nodeOverrides || {};
|
|
352
|
+
|
|
353
|
+
const prefixedId = (id) => `${prefix}${id}`;
|
|
354
|
+
|
|
355
|
+
const nodes = sub.nodes.map((n) => ({
|
|
356
|
+
...n,
|
|
357
|
+
id: prefixedId(n.id),
|
|
358
|
+
config: { ...n.config, ...configOverrides, ...(nodeOverrides[n.id] || {}) },
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
const edges = sub.edges.map((e) => ({
|
|
362
|
+
...e,
|
|
363
|
+
id: `${prefixedId(e.source)}->${prefixedId(e.target)}`,
|
|
364
|
+
source: prefixedId(e.source),
|
|
365
|
+
target: prefixedId(e.target),
|
|
366
|
+
}));
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
nodes,
|
|
370
|
+
edges,
|
|
371
|
+
entryNodeId: prefixedId(sub.meta.entryNode),
|
|
372
|
+
exitNodeId: prefixedId(sub.meta.exitNode),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Connect two sub-workflow embeds (or any two nodes) with an edge.
|
|
378
|
+
* Convenience for wiring exitNodeId → entryNodeId between composed fragments.
|
|
379
|
+
*
|
|
380
|
+
* @param {string} fromNodeId - Source node ID (typically exitNodeId of previous embed)
|
|
381
|
+
* @param {string} toNodeId - Target node ID (typically entryNodeId of next embed)
|
|
382
|
+
* @param {object} [opts] - Edge options (condition, port, backEdge, etc.)
|
|
383
|
+
* @returns {object} Edge definition
|
|
384
|
+
*/
|
|
385
|
+
export function wire(fromNodeId, toNodeId, opts = {}) {
|
|
386
|
+
return edge(fromNodeId, toNodeId, opts);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Factory: build a complete agent pipeline template from phase definitions.
|
|
391
|
+
*
|
|
392
|
+
* Replaces the repetitive trigger → plan → implement → verify → done pattern
|
|
393
|
+
* found across all task-execution templates. Only the phases differ.
|
|
394
|
+
*
|
|
395
|
+
* @param {object} opts
|
|
396
|
+
* @param {string} opts.id - Template ID
|
|
397
|
+
* @param {string} opts.name - Template display name
|
|
398
|
+
* @param {string} opts.description - Template description
|
|
399
|
+
* @param {string} opts.taskPattern - Regex for trigger.task_assigned matching
|
|
400
|
+
* @param {Array<{id:string, label:string, prompt:string}>} opts.phases - Agent phases
|
|
401
|
+
* @param {string[]} [opts.tags] - Metadata tags
|
|
402
|
+
* @param {object} [opts.variables] - Variable overrides (merged with defaults)
|
|
403
|
+
* @param {object} [opts.metadata] - Metadata overrides
|
|
404
|
+
* @returns {object} Complete workflow template definition
|
|
405
|
+
*/
|
|
406
|
+
export function makeAgentPipeline(opts) {
|
|
407
|
+
if (!opts?.id) throw new Error("makeAgentPipeline: id is required");
|
|
408
|
+
if (!opts?.taskPattern) throw new Error("makeAgentPipeline: taskPattern is required");
|
|
409
|
+
if (!Array.isArray(opts?.phases) || opts.phases.length === 0) {
|
|
410
|
+
throw new Error("makeAgentPipeline: at least one phase is required");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
resetLayout();
|
|
414
|
+
|
|
415
|
+
const defaultVariables = {
|
|
416
|
+
taskTimeoutMs: 21600000,
|
|
417
|
+
maxRetries: 2,
|
|
418
|
+
maxContinues: 3,
|
|
419
|
+
testCommand: "auto",
|
|
420
|
+
buildCommand: "auto",
|
|
421
|
+
lintCommand: "auto",
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const triggerNode = node("trigger", "trigger.task_assigned", "Task Assigned", {
|
|
425
|
+
taskPattern: opts.taskPattern,
|
|
426
|
+
}, { x: 400, y: 50 });
|
|
427
|
+
|
|
428
|
+
const yStart = 180;
|
|
429
|
+
const yStep = 160;
|
|
430
|
+
const phaseNodes = opts.phases.map((phase, i) =>
|
|
431
|
+
agentPhase(phase.id, phase.label, phase.prompt, phase.extra || {}, {
|
|
432
|
+
x: 400,
|
|
433
|
+
y: yStart + i * yStep,
|
|
434
|
+
}),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const doneNode = node("done", "notify.log", "Complete", {
|
|
438
|
+
message: opts.doneMessage || `${opts.name} completed.`,
|
|
439
|
+
}, { x: 400, y: yStart + opts.phases.length * yStep });
|
|
440
|
+
|
|
441
|
+
const allNodes = [triggerNode, ...phaseNodes, doneNode];
|
|
442
|
+
|
|
443
|
+
// Linear pipeline: trigger → phase0 → phase1 → … → done
|
|
444
|
+
const edges = [];
|
|
445
|
+
edges.push(edge("trigger", opts.phases[0].id));
|
|
446
|
+
for (let i = 0; i < opts.phases.length - 1; i++) {
|
|
447
|
+
edges.push(edge(opts.phases[i].id, opts.phases[i + 1].id));
|
|
448
|
+
}
|
|
449
|
+
edges.push(edge(opts.phases[opts.phases.length - 1].id, "done"));
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
id: opts.id,
|
|
453
|
+
name: opts.name,
|
|
454
|
+
description: opts.description || "",
|
|
455
|
+
category: opts.category || "task-execution",
|
|
456
|
+
enabled: true,
|
|
457
|
+
recommended: opts.recommended !== false,
|
|
458
|
+
trigger: "trigger.task_assigned",
|
|
459
|
+
variables: { ...defaultVariables, ...opts.variables },
|
|
460
|
+
metadata: {
|
|
461
|
+
author: "bosun",
|
|
462
|
+
version: 1,
|
|
463
|
+
createdAt: "2025-06-01T00:00:00Z",
|
|
464
|
+
templateVersion: "1.0.0",
|
|
465
|
+
tags: opts.tags || [],
|
|
466
|
+
resolveMode: "library",
|
|
467
|
+
...(opts.metadata || {}),
|
|
468
|
+
},
|
|
469
|
+
nodes: allNodes,
|
|
470
|
+
edges,
|
|
471
|
+
};
|
|
472
|
+
}
|