bosun 0.42.2 → 0.42.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/.env.example +9 -0
- package/agent/agent-event-bus.mjs +10 -0
- package/agent/agent-supervisor.mjs +20 -0
- package/bosun-tui.mjs +107 -105
- package/cli.mjs +10 -0
- package/config/config.mjs +25 -0
- package/config/executor-config.mjs +124 -1
- package/infra/container-runner.mjs +565 -1
- package/infra/monitor.mjs +18 -0
- package/infra/tracing.mjs +544 -240
- package/infra/tui-bridge.mjs +13 -1
- package/kanban/kanban-adapter.mjs +128 -4
- package/lib/repo-map.mjs +114 -3
- package/package.json +11 -4
- package/server/ui-server.mjs +3 -0
- package/task/task-archiver.mjs +18 -6
- package/task/task-attachments.mjs +14 -10
- package/task/task-cli.mjs +24 -4
- package/task/task-executor.mjs +19 -0
- package/task/task-store.mjs +194 -37
- package/telegram/telegram-bot.mjs +4 -1
- package/tui/app.mjs +131 -171
- package/tui/components/status-header.mjs +178 -75
- package/tui/lib/header-config.mjs +68 -0
- package/tui/lib/ws-bridge.mjs +61 -9
- package/tui/screens/agents.mjs +127 -0
- package/tui/screens/tasks.mjs +1 -48
- package/ui/app.js +8 -5
- package/ui/components/kanban-board.js +65 -3
- package/ui/components/session-list.js +18 -32
- package/ui/demo-defaults.js +52 -2
- package/ui/modules/session-api.js +100 -0
- package/ui/modules/state.js +71 -15
- package/ui/tabs/workflows.js +25 -1
- package/ui/tui/App.js +298 -0
- package/ui/tui/TasksScreen.js +564 -0
- package/ui/tui/constants.js +55 -0
- package/ui/tui/tasks-screen-helpers.js +301 -0
- package/ui/tui/useTasks.js +61 -0
- package/ui/tui/useWebSocket.js +166 -0
- package/ui/tui/useWorkflows.js +30 -0
- package/workflow/workflow-engine.mjs +412 -7
- package/workflow/workflow-nodes.mjs +616 -75
- package/workflow-templates/agents.mjs +3 -0
- package/workflow-templates/planning.mjs +7 -0
- package/workflow-templates/sub-workflows.mjs +5 -0
- package/workflow-templates/task-execution.mjs +3 -0
- package/workspace/command-diagnostics.mjs +1 -1
- package/workspace/context-cache.mjs +182 -9
|
@@ -39,6 +39,7 @@ import { getToolsPromptBlock } from "../agent/agent-custom-tools.mjs";
|
|
|
39
39
|
import { buildRelevantSkillsPromptBlock, emitSkillInvokeEvent, findRelevantSkills } from "../agent/bosun-skills.mjs";
|
|
40
40
|
import { readBenchmarkModeState, taskMatchesBenchmarkMode } from "../bench/benchmark-mode.mjs";
|
|
41
41
|
import { getSessionTracker } from "../infra/session-tracker.mjs";
|
|
42
|
+
import { runInIsolatedRunner } from "../infra/container-runner.mjs";
|
|
42
43
|
import { recordWorktreeRecoveryEvent } from "../infra/worktree-recovery-state.mjs";
|
|
43
44
|
import {
|
|
44
45
|
appendKnowledgeEntry,
|
|
@@ -57,7 +58,7 @@ import { loadConfig } from "../config/config.mjs";
|
|
|
57
58
|
import { fixGitConfigCorruption } from "../workspace/worktree-manager.mjs";
|
|
58
59
|
import { clearBlockedWorktreeIdentity, normalizeBaseBranch } from "../git/git-safety.mjs";
|
|
59
60
|
import { getBosunCoAuthorTrailer, shouldAddBosunCoAuthor } from "../git/git-commit-helpers.mjs";
|
|
60
|
-
import { buildArchitectEditorFrame } from "../lib/repo-map.mjs";
|
|
61
|
+
import { buildArchitectEditorFrame, buildRepoTopologyContext, hasRepoMapContext } from "../lib/repo-map.mjs";
|
|
61
62
|
import { getGitHubToken, invalidateTokenType } from "../github/github-auth-manager.mjs";
|
|
62
63
|
import {
|
|
63
64
|
CUSTOM_NODE_DIR_NAME,
|
|
@@ -1448,6 +1449,208 @@ async function compactWorkflowCommandResult({
|
|
|
1448
1449
|
};
|
|
1449
1450
|
}
|
|
1450
1451
|
|
|
1452
|
+
function isValidationSandboxFailureText(text) {
|
|
1453
|
+
return /(?:sandbox|operation not permitted|permission denied|access is denied|read-only file system|EPERM|EACCES|denied by policy|seccomp)/i.test(
|
|
1454
|
+
String(text || ""),
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function isValidationBootstrapFailureText(text) {
|
|
1459
|
+
return /(?:\bENOENT\b|spawn\s+.+\s+ENOENT|not recognized as an internal or external command|is not recognized as a name of a cmdlet|command not found|executable file not found|no such file or directory|cannot find the file|failed to start|startup failure)/i.test(
|
|
1460
|
+
String(text || ""),
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function buildValidationFailureDiagnostic({
|
|
1465
|
+
command = "",
|
|
1466
|
+
args = [],
|
|
1467
|
+
status = "error",
|
|
1468
|
+
exitCode = null,
|
|
1469
|
+
stderr = "",
|
|
1470
|
+
output = "",
|
|
1471
|
+
timeoutMs = null,
|
|
1472
|
+
blocked = false,
|
|
1473
|
+
failureDiagnostic = null,
|
|
1474
|
+
} = {}) {
|
|
1475
|
+
if (failureDiagnostic && typeof failureDiagnostic === "object") return failureDiagnostic;
|
|
1476
|
+
const normalizedStatus = String(status || "error").trim().toLowerCase();
|
|
1477
|
+
if (!blocked && normalizedStatus === "success" && Number(exitCode ?? 0) === 0) {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
const combinedText = [stderr, output]
|
|
1481
|
+
.map((value) => String(value || "").trim())
|
|
1482
|
+
.find(Boolean) || "";
|
|
1483
|
+
|
|
1484
|
+
let category = "command_failure";
|
|
1485
|
+
let retryable = false;
|
|
1486
|
+
let summary = `Validation command exited with code ${exitCode ?? "unknown"}.`;
|
|
1487
|
+
|
|
1488
|
+
if (blocked || normalizedStatus === "blocked") {
|
|
1489
|
+
category = "runner_unavailable";
|
|
1490
|
+
retryable = true;
|
|
1491
|
+
summary = "Isolated runner was unavailable before the validation command started.";
|
|
1492
|
+
} else if (normalizedStatus === "timeout" || /(?:timed out|ETIMEDOUT|SIGTERM)/i.test(combinedText)) {
|
|
1493
|
+
category = "timeout";
|
|
1494
|
+
retryable = true;
|
|
1495
|
+
const numericTimeoutMs = timeoutMs != null ? Number(timeoutMs) : NaN;
|
|
1496
|
+
if (Number.isFinite(numericTimeoutMs) && numericTimeoutMs > 0) {
|
|
1497
|
+
summary = `Validation timed out after ${numericTimeoutMs}ms.`;
|
|
1498
|
+
} else {
|
|
1499
|
+
summary = "Validation timed out after the configured timeout.";
|
|
1500
|
+
}
|
|
1501
|
+
} else if (isValidationSandboxFailureText(combinedText)) {
|
|
1502
|
+
category = "sandbox_error";
|
|
1503
|
+
retryable = false;
|
|
1504
|
+
summary = "Validation was blocked by sandbox or filesystem restrictions.";
|
|
1505
|
+
} else if (isValidationBootstrapFailureText(combinedText) || normalizedStatus === "error" && (exitCode == null || Number(exitCode) < 0)) {
|
|
1506
|
+
category = "bootstrap_failure";
|
|
1507
|
+
retryable = true;
|
|
1508
|
+
summary = "Validation could not start cleanly.";
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
const detail = String(combinedText || "").trim().split(/\r?\n/).find(Boolean) || "";
|
|
1512
|
+
return {
|
|
1513
|
+
category,
|
|
1514
|
+
retryable,
|
|
1515
|
+
summary,
|
|
1516
|
+
detail,
|
|
1517
|
+
status: normalizedStatus,
|
|
1518
|
+
exitCode: exitCode ?? null,
|
|
1519
|
+
blocked: blocked === true,
|
|
1520
|
+
command,
|
|
1521
|
+
args: Array.isArray(args) ? [...args] : [],
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
function didValidationCommandPass(result = {}) {
|
|
1526
|
+
if (!result || result.blocked === true || result.failureDiagnostic) return false;
|
|
1527
|
+
const status = String(result.status || "success").trim().toLowerCase();
|
|
1528
|
+
if (status && status !== "success") return false;
|
|
1529
|
+
return Number(result.exitCode ?? 0) === 0;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
function buildValidationResult({
|
|
1533
|
+
passed,
|
|
1534
|
+
exitCode = null,
|
|
1535
|
+
blocked = false,
|
|
1536
|
+
compacted = {},
|
|
1537
|
+
extras = {},
|
|
1538
|
+
failureDiagnostic = null,
|
|
1539
|
+
} = {}) {
|
|
1540
|
+
return {
|
|
1541
|
+
passed,
|
|
1542
|
+
exitCode,
|
|
1543
|
+
blocked,
|
|
1544
|
+
...(failureDiagnostic
|
|
1545
|
+
? {
|
|
1546
|
+
failureKind: failureDiagnostic.category || null,
|
|
1547
|
+
retryable: failureDiagnostic.retryable === true,
|
|
1548
|
+
failureDiagnostic,
|
|
1549
|
+
}
|
|
1550
|
+
: {}),
|
|
1551
|
+
...compacted,
|
|
1552
|
+
...extras,
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
function buildIsolatedRunnerResultExtras(result, lane) {
|
|
1557
|
+
const artifacts = Array.isArray(result?.artifacts) ? result.artifacts : [];
|
|
1558
|
+
return {
|
|
1559
|
+
isolatedRunner: {
|
|
1560
|
+
lane: lane?.lane || "main",
|
|
1561
|
+
reason: lane?.reason || "unknown",
|
|
1562
|
+
provider: result?.provider || null,
|
|
1563
|
+
leaseId: result?.leaseId || null,
|
|
1564
|
+
artifactRoot: result?.artifactRoot || null,
|
|
1565
|
+
attempts: result?.attempts || 1,
|
|
1566
|
+
blocked: result?.blocked === true,
|
|
1567
|
+
failureDiagnostic: result?.failureDiagnostic || null,
|
|
1568
|
+
artifacts,
|
|
1569
|
+
},
|
|
1570
|
+
artifactRoot: result?.artifactRoot || null,
|
|
1571
|
+
artifacts,
|
|
1572
|
+
artifactPaths: artifacts.map((artifact) => artifact.path).filter(Boolean),
|
|
1573
|
+
artifactRetrieveCommands: artifacts
|
|
1574
|
+
.map((artifact) => artifact.retrieveCommand)
|
|
1575
|
+
.filter(Boolean),
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function resolveWorkflowCommandLane({ nodeType, commandType, command, engine }) {
|
|
1580
|
+
const scheduler = engine?.services?.scheduler;
|
|
1581
|
+
if (scheduler && typeof scheduler.selectWorkflowLane === "function") {
|
|
1582
|
+
return scheduler.selectWorkflowLane({ nodeType, commandType, command });
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
const normalizedNodeType = String(nodeType || "").trim();
|
|
1586
|
+
if (['validation.tests', 'validation.build', 'validation.lint'].includes(normalizedNodeType)) {
|
|
1587
|
+
return { lane: "isolated", reason: `workflow_node:${normalizedNodeType}`, heavy: true };
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
const normalizedCommandType = String(commandType || "").trim();
|
|
1591
|
+
if (["test", "build", "qualityGate"].includes(normalizedCommandType)) {
|
|
1592
|
+
return { lane: "isolated", reason: `command_type:${normalizedCommandType}`, heavy: true };
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
const rawCommand = String(command || "");
|
|
1596
|
+
if (/(?:\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?test\b|\b(?:npm|pnpm|yarn|bun)\s+run\s+build\b|\b(?:npm|pnpm|yarn|bun)\s+run\s+lint\b|\bpre-?push\b|\bgit\s+diff\b|\bvitest\b|\bjest\b|\btsc\b)/i.test(rawCommand)) {
|
|
1597
|
+
return { lane: "isolated", reason: "command_pattern:default", heavy: true };
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
return { lane: "main", reason: "lightweight", heavy: false };
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
async function maybeRunWorkflowCommandInIsolation({
|
|
1604
|
+
node,
|
|
1605
|
+
ctx,
|
|
1606
|
+
engine,
|
|
1607
|
+
nodeType,
|
|
1608
|
+
command,
|
|
1609
|
+
args = [],
|
|
1610
|
+
cwd,
|
|
1611
|
+
timeoutMs,
|
|
1612
|
+
env = {},
|
|
1613
|
+
commandType = "",
|
|
1614
|
+
sessionType = "flow",
|
|
1615
|
+
} = {}) {
|
|
1616
|
+
const lane = resolveWorkflowCommandLane({ nodeType, commandType, command, engine });
|
|
1617
|
+
if (lane?.lane !== "isolated") return null;
|
|
1618
|
+
|
|
1619
|
+
ctx.log(node.id, `Offloading ${commandType || nodeType} to isolated runner (${lane.reason})`);
|
|
1620
|
+
const runner = engine?.services?.isolatedRunner;
|
|
1621
|
+
const execute = typeof runner?.run === "function" ? runner.run.bind(runner) : runInIsolatedRunner;
|
|
1622
|
+
const isolated = await execute({
|
|
1623
|
+
command,
|
|
1624
|
+
args,
|
|
1625
|
+
cwd,
|
|
1626
|
+
timeoutMs,
|
|
1627
|
+
env,
|
|
1628
|
+
requestType: commandType || nodeType,
|
|
1629
|
+
taskId: `${node.id || nodeType || "validation"}`,
|
|
1630
|
+
metadata: {
|
|
1631
|
+
nodeId: node.id || null,
|
|
1632
|
+
nodeType,
|
|
1633
|
+
commandType,
|
|
1634
|
+
},
|
|
1635
|
+
});
|
|
1636
|
+
const compacted = await compactWorkflowCommandResult({
|
|
1637
|
+
command,
|
|
1638
|
+
args,
|
|
1639
|
+
output: isolated?.stdout || "",
|
|
1640
|
+
stderr: isolated?.stderr || isolated?.error || "",
|
|
1641
|
+
exitCode: isolated?.exitCode,
|
|
1642
|
+
durationMs: isolated?.duration,
|
|
1643
|
+
sessionType,
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
return {
|
|
1647
|
+
lane,
|
|
1648
|
+
isolated,
|
|
1649
|
+
compacted,
|
|
1650
|
+
extras: buildIsolatedRunnerResultExtras(isolated, lane),
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1451
1654
|
function resolveWorkflowNodeValue(value, ctx) {
|
|
1452
1655
|
if (typeof value === "string") {
|
|
1453
1656
|
const resolved = ctx.resolve(value);
|
|
@@ -1872,7 +2075,7 @@ registerBuiltinNodeType("trigger.manual", {
|
|
|
1872
2075
|
type: "object",
|
|
1873
2076
|
properties: {},
|
|
1874
2077
|
},
|
|
1875
|
-
async execute(node, ctx) {
|
|
2078
|
+
async execute(node, ctx, engine) {
|
|
1876
2079
|
ctx.log(node.id, "Manual trigger fired");
|
|
1877
2080
|
return { triggered: true, reason: "manual" };
|
|
1878
2081
|
},
|
|
@@ -1892,7 +2095,7 @@ registerBuiltinNodeType("trigger.task_low", {
|
|
|
1892
2095
|
countDraftTasks: { type: "boolean", default: false, description: "Also count draft tasks toward threshold" },
|
|
1893
2096
|
},
|
|
1894
2097
|
},
|
|
1895
|
-
async execute(node, ctx) {
|
|
2098
|
+
async execute(node, ctx, engine) {
|
|
1896
2099
|
const threshold = node.config?.threshold ?? 3;
|
|
1897
2100
|
const status = node.config?.status ?? "todo";
|
|
1898
2101
|
const countDrafts = node.config?.countDraftTasks === true;
|
|
@@ -1953,7 +2156,7 @@ registerBuiltinNodeType("trigger.schedule", {
|
|
|
1953
2156
|
cron: { type: "string", description: "Cron expression (future support)" },
|
|
1954
2157
|
},
|
|
1955
2158
|
},
|
|
1956
|
-
async execute(node, ctx) {
|
|
2159
|
+
async execute(node, ctx, engine) {
|
|
1957
2160
|
const interval = node.config?.intervalMs ?? 3600000;
|
|
1958
2161
|
const lastRun = ctx.data?._lastRunAt ?? 0;
|
|
1959
2162
|
const elapsed = Date.now() - lastRun;
|
|
@@ -1972,7 +2175,7 @@ registerBuiltinNodeType("trigger.event", {
|
|
|
1972
2175
|
filter: { type: "string", description: "Optional filter expression" },
|
|
1973
2176
|
},
|
|
1974
2177
|
},
|
|
1975
|
-
async execute(node, ctx) {
|
|
2178
|
+
async execute(node, ctx, engine) {
|
|
1976
2179
|
const expected = node.config?.eventType;
|
|
1977
2180
|
const actual = ctx.data?.eventType || ctx.eventType;
|
|
1978
2181
|
const triggered = expected === actual;
|
|
@@ -2018,7 +2221,7 @@ registerBuiltinNodeType("trigger.meeting.wake_phrase", {
|
|
|
2018
2221
|
},
|
|
2019
2222
|
},
|
|
2020
2223
|
},
|
|
2021
|
-
async execute(node, ctx) {
|
|
2224
|
+
async execute(node, ctx, engine) {
|
|
2022
2225
|
const eventData = ctx.data && typeof ctx.data === "object" ? ctx.data : {};
|
|
2023
2226
|
const resolveValue = (value) => (
|
|
2024
2227
|
typeof ctx?.resolve === "function" ? ctx.resolve(value) : value
|
|
@@ -2156,7 +2359,7 @@ registerBuiltinNodeType("trigger.webhook", {
|
|
|
2156
2359
|
method: { type: "string", default: "POST", enum: ["GET", "POST"] },
|
|
2157
2360
|
},
|
|
2158
2361
|
},
|
|
2159
|
-
async execute(node, ctx) {
|
|
2362
|
+
async execute(node, ctx, engine) {
|
|
2160
2363
|
return { triggered: true, payload: ctx.data?.webhookPayload || {} };
|
|
2161
2364
|
},
|
|
2162
2365
|
});
|
|
@@ -2175,7 +2378,7 @@ registerBuiltinNodeType("trigger.pr_event", {
|
|
|
2175
2378
|
branchPattern: { type: "string", description: "Branch name regex filter" },
|
|
2176
2379
|
},
|
|
2177
2380
|
},
|
|
2178
|
-
async execute(node, ctx) {
|
|
2381
|
+
async execute(node, ctx, engine) {
|
|
2179
2382
|
const expectedEvents = [
|
|
2180
2383
|
node.config?.event,
|
|
2181
2384
|
...(Array.isArray(node.config?.events) ? node.config.events : []),
|
|
@@ -2208,7 +2411,7 @@ registerBuiltinNodeType("trigger.task_assigned", {
|
|
|
2208
2411
|
filter: { type: "string", description: "JS expression filter (e.g., \"task.tags?.includes('backend')\")" },
|
|
2209
2412
|
},
|
|
2210
2413
|
},
|
|
2211
|
-
async execute(node, ctx) {
|
|
2414
|
+
async execute(node, ctx, engine) {
|
|
2212
2415
|
const triggered = evaluateTaskAssignedTriggerConfig(node.config, ctx.data);
|
|
2213
2416
|
return { triggered, task: ctx.data };
|
|
2214
2417
|
},
|
|
@@ -2224,7 +2427,7 @@ registerBuiltinNodeType("trigger.anomaly", {
|
|
|
2224
2427
|
agentFilter: { type: "string", description: "Regex to match agent ID or name" },
|
|
2225
2428
|
},
|
|
2226
2429
|
},
|
|
2227
|
-
async execute(node, ctx) {
|
|
2430
|
+
async execute(node, ctx, engine) {
|
|
2228
2431
|
const expected = node.config?.anomalyType;
|
|
2229
2432
|
const actual = ctx.data?.anomalyType || ctx.data?.type;
|
|
2230
2433
|
const typeMatch = !expected || expected === actual;
|
|
@@ -2264,7 +2467,7 @@ registerBuiltinNodeType("trigger.scheduled_once", {
|
|
|
2264
2467
|
},
|
|
2265
2468
|
required: ["runAt"],
|
|
2266
2469
|
},
|
|
2267
|
-
async execute(node, ctx) {
|
|
2470
|
+
async execute(node, ctx, engine) {
|
|
2268
2471
|
const rawRunAt = ctx.resolve(node.config?.runAt || "");
|
|
2269
2472
|
let runAtMs;
|
|
2270
2473
|
|
|
@@ -2315,7 +2518,7 @@ registerBuiltinNodeType("trigger.workflow_call", {
|
|
|
2315
2518
|
},
|
|
2316
2519
|
},
|
|
2317
2520
|
},
|
|
2318
|
-
async execute(node, ctx) {
|
|
2521
|
+
async execute(node, ctx, engine) {
|
|
2319
2522
|
// Validate required inputs from _triggerVars or context data
|
|
2320
2523
|
const inputDefs = node.config?.inputs || {};
|
|
2321
2524
|
const callerVars = ctx.data?._triggerVars || ctx.data || {};
|
|
@@ -2365,7 +2568,7 @@ registerBuiltinNodeType("condition.expression", {
|
|
|
2365
2568
|
},
|
|
2366
2569
|
required: ["expression"],
|
|
2367
2570
|
},
|
|
2368
|
-
async execute(node, ctx) {
|
|
2571
|
+
async execute(node, ctx, engine) {
|
|
2369
2572
|
const expr = node.config?.expression;
|
|
2370
2573
|
if (!expr) throw new Error("Expression is required");
|
|
2371
2574
|
try {
|
|
@@ -2391,7 +2594,7 @@ registerBuiltinNodeType("condition.task_has_tag", {
|
|
|
2391
2594
|
},
|
|
2392
2595
|
required: ["tag"],
|
|
2393
2596
|
},
|
|
2394
|
-
async execute(node, ctx) {
|
|
2597
|
+
async execute(node, ctx, engine) {
|
|
2395
2598
|
const tag = node.config?.tag?.toLowerCase();
|
|
2396
2599
|
const field = node.config?.field || "tags";
|
|
2397
2600
|
let haystack = ctx.data?.task?.[field] || ctx.data?.[field] || "";
|
|
@@ -2412,7 +2615,7 @@ registerBuiltinNodeType("condition.file_exists", {
|
|
|
2412
2615
|
},
|
|
2413
2616
|
required: ["path"],
|
|
2414
2617
|
},
|
|
2415
|
-
async execute(node, ctx) {
|
|
2618
|
+
async execute(node, ctx, engine) {
|
|
2416
2619
|
const filePath = ctx.resolve(node.config?.path || "");
|
|
2417
2620
|
const exists = existsSync(filePath);
|
|
2418
2621
|
ctx.log(node.id, `File check: "${filePath}" → ${exists}`);
|
|
@@ -2436,7 +2639,7 @@ registerBuiltinNodeType("condition.switch", {
|
|
|
2436
2639
|
},
|
|
2437
2640
|
required: [],
|
|
2438
2641
|
},
|
|
2439
|
-
async execute(node, ctx) {
|
|
2642
|
+
async execute(node, ctx, engine) {
|
|
2440
2643
|
let value;
|
|
2441
2644
|
const expr = node.config?.value || node.config?.expression || "";
|
|
2442
2645
|
if (expr) {
|
|
@@ -2615,10 +2818,12 @@ registerBuiltinNodeType("action.run_agent", {
|
|
|
2615
2818
|
);
|
|
2616
2819
|
}
|
|
2617
2820
|
let finalPrompt = prompt;
|
|
2821
|
+
const promptHasRepoMapContext = hasRepoMapContext(finalPrompt);
|
|
2618
2822
|
const architectEditorFrame = buildArchitectEditorFrame({
|
|
2619
2823
|
executionRole: ctx.resolve(node.config?.executionRole || ""),
|
|
2620
2824
|
architectPlan,
|
|
2621
2825
|
planSummary: architectPlan,
|
|
2826
|
+
includeRepoMap: !promptHasRepoMapContext,
|
|
2622
2827
|
repoMap: node.config?.repoMap || ctx.data?.repoMap || null,
|
|
2623
2828
|
repoMapFileLimit: node.config?.repoMapFileLimit,
|
|
2624
2829
|
repoMapQuery: ctx.resolve(node.config?.repoMapQuery || ""),
|
|
@@ -3343,7 +3548,7 @@ registerBuiltinNodeType("action.run_command", {
|
|
|
3343
3548
|
},
|
|
3344
3549
|
required: ["command"],
|
|
3345
3550
|
},
|
|
3346
|
-
async execute(node, ctx) {
|
|
3551
|
+
async execute(node, ctx, engine) {
|
|
3347
3552
|
const resolvedCommand = ctx.resolve(node.config?.command || "");
|
|
3348
3553
|
const cwd = ctx.resolve(node.config?.cwd || ctx.data?.worktreePath || process.cwd());
|
|
3349
3554
|
const commandType = typeof node.config?.commandType === "string" ? node.config.commandType.trim() : "";
|
|
@@ -3409,7 +3614,43 @@ registerBuiltinNodeType("action.run_command", {
|
|
|
3409
3614
|
if (command !== autoResolvedCommand) {
|
|
3410
3615
|
ctx.log(node.id, `Normalized legacy command for portability: ${command}`);
|
|
3411
3616
|
}
|
|
3412
|
-
|
|
3617
|
+
const displayCommand = usedArgv ? `${command} ${commandArgs.join(" ")}`.trim() : command;
|
|
3618
|
+
ctx.log(node.id, `Running: ${displayCommand}`);
|
|
3619
|
+
const isolatedRun = await maybeRunWorkflowCommandInIsolation({
|
|
3620
|
+
node,
|
|
3621
|
+
ctx,
|
|
3622
|
+
engine,
|
|
3623
|
+
nodeType: "action.run_command",
|
|
3624
|
+
command,
|
|
3625
|
+
args: commandArgs,
|
|
3626
|
+
cwd,
|
|
3627
|
+
timeoutMs: timeout,
|
|
3628
|
+
env: commandEnv,
|
|
3629
|
+
commandType,
|
|
3630
|
+
});
|
|
3631
|
+
if (isolatedRun) {
|
|
3632
|
+
if (shouldParseJson) {
|
|
3633
|
+
const parsedOutput = parseOutput(isolatedRun.isolated?.stdout || isolatedRun.compacted.output || "");
|
|
3634
|
+
return {
|
|
3635
|
+
success: isolatedRun.isolated?.blocked !== true && Number(isolatedRun.isolated?.exitCode ?? 0) === 0,
|
|
3636
|
+
exitCode: isolatedRun.isolated?.exitCode ?? null,
|
|
3637
|
+
output: parsedOutput,
|
|
3638
|
+
...isolatedRun.extras,
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
const result = {
|
|
3642
|
+
success: isolatedRun.isolated?.blocked !== true && Number(isolatedRun.isolated?.exitCode ?? 0) === 0,
|
|
3643
|
+
exitCode: isolatedRun.isolated?.exitCode ?? null,
|
|
3644
|
+
blocked: isolatedRun.isolated?.blocked === true,
|
|
3645
|
+
error: isolatedRun.isolated?.error || null,
|
|
3646
|
+
...isolatedRun.compacted,
|
|
3647
|
+
...isolatedRun.extras,
|
|
3648
|
+
};
|
|
3649
|
+
if (node.config?.failOnError && !result.success) {
|
|
3650
|
+
throw new Error(trimLogText(result.output || result.error || "command failed", 400));
|
|
3651
|
+
}
|
|
3652
|
+
return result;
|
|
3653
|
+
}
|
|
3413
3654
|
const startedAt = Date.now();
|
|
3414
3655
|
try {
|
|
3415
3656
|
const output = usedArgv
|
|
@@ -4594,7 +4835,7 @@ registerBuiltinNodeType("action.git_operations", {
|
|
|
4594
4835
|
},
|
|
4595
4836
|
required: [],
|
|
4596
4837
|
},
|
|
4597
|
-
async execute(node, ctx) {
|
|
4838
|
+
async execute(node, ctx, engine) {
|
|
4598
4839
|
const cwd = ctx.resolve(node.config?.cwd || ctx.data?.worktreePath || process.cwd());
|
|
4599
4840
|
const resolveOpCommand = (opConfig = {}) => {
|
|
4600
4841
|
const op = String(opConfig.op || opConfig.operation || "").trim();
|
|
@@ -4736,7 +4977,7 @@ registerBuiltinNodeType("action.create_pr", {
|
|
|
4736
4977
|
},
|
|
4737
4978
|
required: ["title"],
|
|
4738
4979
|
},
|
|
4739
|
-
async execute(node, ctx) {
|
|
4980
|
+
async execute(node, ctx, engine) {
|
|
4740
4981
|
const title = ctx.resolve(node.config?.title || "");
|
|
4741
4982
|
const body = ctx.resolve(node.config?.body || "");
|
|
4742
4983
|
const baseInput = ctx.resolve(node.config?.base || node.config?.baseBranch || "main");
|
|
@@ -5079,7 +5320,7 @@ registerBuiltinNodeType("action.write_file", {
|
|
|
5079
5320
|
},
|
|
5080
5321
|
required: ["path", "content"],
|
|
5081
5322
|
},
|
|
5082
|
-
async execute(node, ctx) {
|
|
5323
|
+
async execute(node, ctx, engine) {
|
|
5083
5324
|
const filePath = ctx.resolve(node.config?.path || "");
|
|
5084
5325
|
const content = ctx.resolve(node.config?.content || "");
|
|
5085
5326
|
if (node.config?.mkdir) {
|
|
@@ -5105,7 +5346,7 @@ registerBuiltinNodeType("action.read_file", {
|
|
|
5105
5346
|
},
|
|
5106
5347
|
required: ["path"],
|
|
5107
5348
|
},
|
|
5108
|
-
async execute(node, ctx) {
|
|
5349
|
+
async execute(node, ctx, engine) {
|
|
5109
5350
|
const filePath = ctx.resolve(node.config?.path || "");
|
|
5110
5351
|
if (!existsSync(filePath)) {
|
|
5111
5352
|
return { success: false, error: `File not found: ${filePath}` };
|
|
@@ -5126,7 +5367,7 @@ registerBuiltinNodeType("action.set_variable", {
|
|
|
5126
5367
|
},
|
|
5127
5368
|
required: ["key"],
|
|
5128
5369
|
},
|
|
5129
|
-
async execute(node, ctx) {
|
|
5370
|
+
async execute(node, ctx, engine) {
|
|
5130
5371
|
const key = node.config?.key;
|
|
5131
5372
|
let value = node.config?.value || "";
|
|
5132
5373
|
if (node.config?.isExpression) {
|
|
@@ -5161,7 +5402,7 @@ registerBuiltinNodeType("action.delay", {
|
|
|
5161
5402
|
message: { type: "string", description: "Legacy alias for reason" },
|
|
5162
5403
|
},
|
|
5163
5404
|
},
|
|
5164
|
-
async execute(node, ctx) {
|
|
5405
|
+
async execute(node, ctx, engine) {
|
|
5165
5406
|
const baseMs = Number(
|
|
5166
5407
|
node.config?.ms ??
|
|
5167
5408
|
node.config?.delayMs ??
|
|
@@ -5218,7 +5459,7 @@ registerBuiltinNodeType("validation.screenshot", {
|
|
|
5218
5459
|
},
|
|
5219
5460
|
required: ["url"],
|
|
5220
5461
|
},
|
|
5221
|
-
async execute(node, ctx) {
|
|
5462
|
+
async execute(node, ctx, engine) {
|
|
5222
5463
|
const url = ctx.resolve(node.config?.url || "http://localhost:3000");
|
|
5223
5464
|
const outDir = ctx.resolve(node.config?.outputDir || ".bosun/evidence");
|
|
5224
5465
|
const filename = ctx.resolve(node.config?.filename || `screenshot-${Date.now()}.png`);
|
|
@@ -5441,12 +5682,42 @@ registerBuiltinNodeType("validation.tests", {
|
|
|
5441
5682
|
requiredPassRate: { type: "number", default: 1.0, description: "Minimum pass rate (0-1)" },
|
|
5442
5683
|
},
|
|
5443
5684
|
},
|
|
5444
|
-
async execute(node, ctx) {
|
|
5685
|
+
async execute(node, ctx, engine) {
|
|
5445
5686
|
const command = ctx.resolve(node.config?.command || "npm test");
|
|
5446
5687
|
const cwd = ctx.resolve(node.config?.cwd || ctx.data?.worktreePath || process.cwd());
|
|
5447
5688
|
const timeout = node.config?.timeoutMs || 600000;
|
|
5448
5689
|
|
|
5449
5690
|
ctx.log(node.id, `Running tests: ${command}`);
|
|
5691
|
+
const isolatedRun = await maybeRunWorkflowCommandInIsolation({
|
|
5692
|
+
node,
|
|
5693
|
+
ctx,
|
|
5694
|
+
engine,
|
|
5695
|
+
nodeType: "validation.tests",
|
|
5696
|
+
command,
|
|
5697
|
+
cwd,
|
|
5698
|
+
timeoutMs: timeout,
|
|
5699
|
+
commandType: "test",
|
|
5700
|
+
});
|
|
5701
|
+
if (isolatedRun) {
|
|
5702
|
+
const failureDiagnostic = buildValidationFailureDiagnostic({
|
|
5703
|
+
command,
|
|
5704
|
+
status: isolatedRun.isolated?.status,
|
|
5705
|
+
exitCode: isolatedRun.isolated?.exitCode,
|
|
5706
|
+
stderr: isolatedRun.isolated?.stderr || isolatedRun.isolated?.error || "",
|
|
5707
|
+
output: isolatedRun.isolated?.stdout || "",
|
|
5708
|
+
timeoutMs: timeout,
|
|
5709
|
+
blocked: isolatedRun.isolated?.blocked === true,
|
|
5710
|
+
failureDiagnostic: isolatedRun.isolated?.failureDiagnostic,
|
|
5711
|
+
});
|
|
5712
|
+
return buildValidationResult({
|
|
5713
|
+
passed: didValidationCommandPass({ ...isolatedRun.isolated, failureDiagnostic }),
|
|
5714
|
+
exitCode: isolatedRun.isolated?.exitCode ?? null,
|
|
5715
|
+
blocked: isolatedRun.isolated?.blocked === true,
|
|
5716
|
+
compacted: isolatedRun.compacted,
|
|
5717
|
+
extras: isolatedRun.extras,
|
|
5718
|
+
failureDiagnostic,
|
|
5719
|
+
});
|
|
5720
|
+
}
|
|
5450
5721
|
const startedAt = Date.now();
|
|
5451
5722
|
try {
|
|
5452
5723
|
const output = execSync(command, { cwd, timeout, encoding: "utf8", stdio: "pipe" });
|
|
@@ -5467,7 +5738,20 @@ registerBuiltinNodeType("validation.tests", {
|
|
|
5467
5738
|
exitCode: err.status,
|
|
5468
5739
|
durationMs: Date.now() - startedAt,
|
|
5469
5740
|
});
|
|
5470
|
-
|
|
5741
|
+
const failureDiagnostic = buildValidationFailureDiagnostic({
|
|
5742
|
+
command,
|
|
5743
|
+
status: /(?:timed out|ETIMEDOUT|SIGTERM)/i.test(String(err?.message || "")) ? "timeout" : "error",
|
|
5744
|
+
exitCode: err.status,
|
|
5745
|
+
stderr: err.stderr?.toString() || err.message,
|
|
5746
|
+
output,
|
|
5747
|
+
timeoutMs: timeout,
|
|
5748
|
+
});
|
|
5749
|
+
return buildValidationResult({
|
|
5750
|
+
passed: false,
|
|
5751
|
+
exitCode: err.status,
|
|
5752
|
+
compacted,
|
|
5753
|
+
failureDiagnostic,
|
|
5754
|
+
});
|
|
5471
5755
|
}
|
|
5472
5756
|
},
|
|
5473
5757
|
});
|
|
@@ -5483,7 +5767,7 @@ registerBuiltinNodeType("validation.build", {
|
|
|
5483
5767
|
zeroWarnings: { type: "boolean", default: false, description: "Fail on warnings too" },
|
|
5484
5768
|
},
|
|
5485
5769
|
},
|
|
5486
|
-
async execute(node, ctx) {
|
|
5770
|
+
async execute(node, ctx, engine) {
|
|
5487
5771
|
const resolvedCommand = ctx.resolve(node.config?.command || "npm run build");
|
|
5488
5772
|
const command = normalizeLegacyWorkflowCommand(resolvedCommand);
|
|
5489
5773
|
const cwd = ctx.resolve(node.config?.cwd || ctx.data?.worktreePath || process.cwd());
|
|
@@ -5493,6 +5777,48 @@ registerBuiltinNodeType("validation.build", {
|
|
|
5493
5777
|
ctx.log(node.id, `Normalized legacy command for portability: ${command}`);
|
|
5494
5778
|
}
|
|
5495
5779
|
ctx.log(node.id, `Building: ${command}`);
|
|
5780
|
+
const isolatedRun = await maybeRunWorkflowCommandInIsolation({
|
|
5781
|
+
node,
|
|
5782
|
+
ctx,
|
|
5783
|
+
engine,
|
|
5784
|
+
nodeType: "validation.build",
|
|
5785
|
+
command,
|
|
5786
|
+
cwd,
|
|
5787
|
+
timeoutMs: timeout,
|
|
5788
|
+
commandType: "build",
|
|
5789
|
+
});
|
|
5790
|
+
if (isolatedRun) {
|
|
5791
|
+
const combinedOutput = `${isolatedRun.isolated?.stdout || ""}\n${isolatedRun.isolated?.stderr || ""}`;
|
|
5792
|
+
const hasWarnings = /warning/i.test(combinedOutput);
|
|
5793
|
+
if (node.config?.zeroWarnings && hasWarnings) {
|
|
5794
|
+
return {
|
|
5795
|
+
passed: false,
|
|
5796
|
+
reason: "warnings_found",
|
|
5797
|
+
exitCode: isolatedRun.isolated?.exitCode ?? 0,
|
|
5798
|
+
blocked: isolatedRun.isolated?.blocked === true,
|
|
5799
|
+
...isolatedRun.compacted,
|
|
5800
|
+
...isolatedRun.extras,
|
|
5801
|
+
};
|
|
5802
|
+
}
|
|
5803
|
+
const failureDiagnostic = buildValidationFailureDiagnostic({
|
|
5804
|
+
command,
|
|
5805
|
+
status: isolatedRun.isolated?.status,
|
|
5806
|
+
exitCode: isolatedRun.isolated?.exitCode,
|
|
5807
|
+
stderr: isolatedRun.isolated?.stderr || isolatedRun.isolated?.error || "",
|
|
5808
|
+
output: isolatedRun.isolated?.stdout || "",
|
|
5809
|
+
timeoutMs: timeout,
|
|
5810
|
+
blocked: isolatedRun.isolated?.blocked === true,
|
|
5811
|
+
failureDiagnostic: isolatedRun.isolated?.failureDiagnostic,
|
|
5812
|
+
});
|
|
5813
|
+
return buildValidationResult({
|
|
5814
|
+
passed: didValidationCommandPass({ ...isolatedRun.isolated, failureDiagnostic }),
|
|
5815
|
+
exitCode: isolatedRun.isolated?.exitCode ?? null,
|
|
5816
|
+
blocked: isolatedRun.isolated?.blocked === true,
|
|
5817
|
+
compacted: isolatedRun.compacted,
|
|
5818
|
+
extras: isolatedRun.extras,
|
|
5819
|
+
failureDiagnostic,
|
|
5820
|
+
});
|
|
5821
|
+
}
|
|
5496
5822
|
const startedAt = Date.now();
|
|
5497
5823
|
try {
|
|
5498
5824
|
const output = execSync(command, { cwd, timeout, encoding: "utf8", stdio: "pipe" });
|
|
@@ -5515,7 +5841,20 @@ registerBuiltinNodeType("validation.build", {
|
|
|
5515
5841
|
exitCode: err.status,
|
|
5516
5842
|
durationMs: Date.now() - startedAt,
|
|
5517
5843
|
});
|
|
5518
|
-
|
|
5844
|
+
const failureDiagnostic = buildValidationFailureDiagnostic({
|
|
5845
|
+
command,
|
|
5846
|
+
status: /(?:timed out|ETIMEDOUT|SIGTERM)/i.test(String(err?.message || "")) ? "timeout" : "error",
|
|
5847
|
+
exitCode: err.status,
|
|
5848
|
+
stderr: err.stderr?.toString() || err.message,
|
|
5849
|
+
output: err.stdout?.toString() || "",
|
|
5850
|
+
timeoutMs: timeout,
|
|
5851
|
+
});
|
|
5852
|
+
return buildValidationResult({
|
|
5853
|
+
passed: false,
|
|
5854
|
+
exitCode: err.status,
|
|
5855
|
+
compacted,
|
|
5856
|
+
failureDiagnostic,
|
|
5857
|
+
});
|
|
5519
5858
|
}
|
|
5520
5859
|
},
|
|
5521
5860
|
});
|
|
@@ -5530,15 +5869,46 @@ registerBuiltinNodeType("validation.lint", {
|
|
|
5530
5869
|
timeoutMs: { type: "number", default: 120000 },
|
|
5531
5870
|
},
|
|
5532
5871
|
},
|
|
5533
|
-
async execute(node, ctx) {
|
|
5872
|
+
async execute(node, ctx, engine) {
|
|
5534
5873
|
const command = ctx.resolve(node.config?.command || "npm run lint");
|
|
5535
5874
|
if (!command || !command.trim()) {
|
|
5536
5875
|
return { passed: true, output: "no lint configured", skipped: true };
|
|
5537
5876
|
}
|
|
5538
5877
|
const cwd = ctx.resolve(node.config?.cwd || ctx.data?.worktreePath || process.cwd());
|
|
5878
|
+
const timeout = node.config?.timeoutMs || 120000;
|
|
5879
|
+
const isolatedRun = await maybeRunWorkflowCommandInIsolation({
|
|
5880
|
+
node,
|
|
5881
|
+
ctx,
|
|
5882
|
+
engine,
|
|
5883
|
+
nodeType: "validation.lint",
|
|
5884
|
+
command,
|
|
5885
|
+
cwd,
|
|
5886
|
+
timeoutMs: timeout,
|
|
5887
|
+
commandType: "qualityGate",
|
|
5888
|
+
});
|
|
5889
|
+
if (isolatedRun) {
|
|
5890
|
+
const failureDiagnostic = buildValidationFailureDiagnostic({
|
|
5891
|
+
command,
|
|
5892
|
+
status: isolatedRun.isolated?.status,
|
|
5893
|
+
exitCode: isolatedRun.isolated?.exitCode,
|
|
5894
|
+
stderr: isolatedRun.isolated?.stderr || isolatedRun.isolated?.error || "",
|
|
5895
|
+
output: isolatedRun.isolated?.stdout || "",
|
|
5896
|
+
timeoutMs: timeout,
|
|
5897
|
+
blocked: isolatedRun.isolated?.blocked === true,
|
|
5898
|
+
failureDiagnostic: isolatedRun.isolated?.failureDiagnostic,
|
|
5899
|
+
});
|
|
5900
|
+
return buildValidationResult({
|
|
5901
|
+
passed: didValidationCommandPass({ ...isolatedRun.isolated, failureDiagnostic }),
|
|
5902
|
+
exitCode: isolatedRun.isolated?.exitCode ?? null,
|
|
5903
|
+
blocked: isolatedRun.isolated?.blocked === true,
|
|
5904
|
+
compacted: isolatedRun.compacted,
|
|
5905
|
+
extras: isolatedRun.extras,
|
|
5906
|
+
failureDiagnostic,
|
|
5907
|
+
});
|
|
5908
|
+
}
|
|
5539
5909
|
const startedAt = Date.now();
|
|
5540
5910
|
try {
|
|
5541
|
-
const output = execSync(command, { cwd, timeout
|
|
5911
|
+
const output = execSync(command, { cwd, timeout, encoding: "utf8", stdio: "pipe" });
|
|
5542
5912
|
const compacted = await compactWorkflowCommandResult({
|
|
5543
5913
|
command,
|
|
5544
5914
|
output: output?.trim() || "",
|
|
@@ -5554,7 +5924,20 @@ registerBuiltinNodeType("validation.lint", {
|
|
|
5554
5924
|
exitCode: err.status,
|
|
5555
5925
|
durationMs: Date.now() - startedAt,
|
|
5556
5926
|
});
|
|
5557
|
-
|
|
5927
|
+
const failureDiagnostic = buildValidationFailureDiagnostic({
|
|
5928
|
+
command,
|
|
5929
|
+
status: /(?:timed out|ETIMEDOUT|SIGTERM)/i.test(String(err?.message || "")) ? "timeout" : "error",
|
|
5930
|
+
exitCode: err.status,
|
|
5931
|
+
stderr: err.stderr?.toString() || err.message,
|
|
5932
|
+
output: err.stdout?.toString() || "",
|
|
5933
|
+
timeoutMs: timeout,
|
|
5934
|
+
});
|
|
5935
|
+
return buildValidationResult({
|
|
5936
|
+
passed: false,
|
|
5937
|
+
exitCode: err.status,
|
|
5938
|
+
compacted,
|
|
5939
|
+
failureDiagnostic,
|
|
5940
|
+
});
|
|
5558
5941
|
}
|
|
5559
5942
|
},
|
|
5560
5943
|
});
|
|
@@ -5572,7 +5955,7 @@ registerBuiltinNodeType("transform.json_parse", {
|
|
|
5572
5955
|
field: { type: "string", description: "Field in source output containing JSON" },
|
|
5573
5956
|
},
|
|
5574
5957
|
},
|
|
5575
|
-
async execute(node, ctx) {
|
|
5958
|
+
async execute(node, ctx, engine) {
|
|
5576
5959
|
const sourceId = node.config?.input;
|
|
5577
5960
|
const field = node.config?.field || "output";
|
|
5578
5961
|
let raw = sourceId ? ctx.getNodeOutput(sourceId)?.[field] : ctx.resolve(node.config?.value || "");
|
|
@@ -5594,7 +5977,7 @@ registerBuiltinNodeType("transform.template", {
|
|
|
5594
5977
|
},
|
|
5595
5978
|
required: ["template"],
|
|
5596
5979
|
},
|
|
5597
|
-
async execute(node, ctx) {
|
|
5980
|
+
async execute(node, ctx, engine) {
|
|
5598
5981
|
const result = ctx.resolve(node.config?.template || "");
|
|
5599
5982
|
return { text: result };
|
|
5600
5983
|
},
|
|
@@ -5608,7 +5991,7 @@ registerBuiltinNodeType("transform.aggregate", {
|
|
|
5608
5991
|
sources: { type: "array", items: { type: "string" }, description: "Node IDs to aggregate" },
|
|
5609
5992
|
},
|
|
5610
5993
|
},
|
|
5611
|
-
async execute(node, ctx) {
|
|
5994
|
+
async execute(node, ctx, engine) {
|
|
5612
5995
|
const sources = node.config?.sources || [];
|
|
5613
5996
|
const aggregated = {};
|
|
5614
5997
|
for (const src of sources) {
|
|
@@ -5662,7 +6045,7 @@ registerBuiltinNodeType("transform.llm_parse", {
|
|
|
5662
6045
|
},
|
|
5663
6046
|
required: [],
|
|
5664
6047
|
},
|
|
5665
|
-
async execute(node, ctx) {
|
|
6048
|
+
async execute(node, ctx, engine) {
|
|
5666
6049
|
// Resolve the input text
|
|
5667
6050
|
let text = "";
|
|
5668
6051
|
const inputRef = ctx.resolve(node.config?.input || "");
|
|
@@ -5739,7 +6122,7 @@ registerBuiltinNodeType("notify.log", {
|
|
|
5739
6122
|
},
|
|
5740
6123
|
required: ["message"],
|
|
5741
6124
|
},
|
|
5742
|
-
async execute(node, ctx) {
|
|
6125
|
+
async execute(node, ctx, engine) {
|
|
5743
6126
|
const message = ctx.resolve(node.config?.message || "");
|
|
5744
6127
|
const level = node.config?.level || "info";
|
|
5745
6128
|
ctx.log(node.id, message, level);
|
|
@@ -5793,7 +6176,7 @@ registerBuiltinNodeType("notify.webhook_out", {
|
|
|
5793
6176
|
},
|
|
5794
6177
|
required: ["url"],
|
|
5795
6178
|
},
|
|
5796
|
-
async execute(node, ctx) {
|
|
6179
|
+
async execute(node, ctx, engine) {
|
|
5797
6180
|
const url = ctx.resolve(node.config?.url || "");
|
|
5798
6181
|
const method = node.config?.method || "POST";
|
|
5799
6182
|
const body = node.config?.body ? JSON.stringify(node.config.body) : undefined;
|
|
@@ -5954,7 +6337,7 @@ registerBuiltinNodeType("agent.select_profile", {
|
|
|
5954
6337
|
default: { type: "string", default: "general", description: "Default profile if no match" },
|
|
5955
6338
|
},
|
|
5956
6339
|
},
|
|
5957
|
-
async execute(node, ctx) {
|
|
6340
|
+
async execute(node, ctx, engine) {
|
|
5958
6341
|
const profiles = node.config?.profiles || {};
|
|
5959
6342
|
const taskTitle = (ctx.data?.taskTitle || "").toLowerCase();
|
|
5960
6343
|
const taskTags = (ctx.data?.taskTags || []).map((t) => t.toLowerCase());
|
|
@@ -7169,6 +7552,9 @@ registerBuiltinNodeType("agent.run_planner", {
|
|
|
7169
7552
|
context: { type: "string", description: "Additional context for the planner" },
|
|
7170
7553
|
prompt: { type: "string", description: "Optional explicit planner prompt override" },
|
|
7171
7554
|
outputVariable: { type: "string", description: "Optional context key to store planner output text" },
|
|
7555
|
+
repoMap: { type: "object", description: "Optional explicit repo map context" },
|
|
7556
|
+
repoMapQuery: { type: "string", description: "Optional query used to select a compact repo topology" },
|
|
7557
|
+
repoMapFileLimit: { type: "number", default: 8, description: "Maximum repo-map files to include" },
|
|
7172
7558
|
projectId: { type: "string" },
|
|
7173
7559
|
dedup: { type: "boolean", default: true },
|
|
7174
7560
|
timeoutMs: { type: "number", default: 960000, description: "Node timeout in ms (recommended >= agentTimeoutMs)" },
|
|
@@ -7184,6 +7570,7 @@ registerBuiltinNodeType("agent.run_planner", {
|
|
|
7184
7570
|
const plannerFeedback = resolvePlannerFeedbackContext(ctx.data?._plannerFeedback);
|
|
7185
7571
|
const explicitPrompt = ctx.resolve(node.config?.prompt || "");
|
|
7186
7572
|
const outputVariable = ctx.resolve(node.config?.outputVariable || "");
|
|
7573
|
+
const repoMapQuery = ctx.resolve(node.config?.repoMapQuery || "");
|
|
7187
7574
|
const configuredNodeTimeout = Number(ctx.resolve(node.config?.timeoutMs || node.config?.timeout || 0));
|
|
7188
7575
|
const configuredAgentTimeout = Number(ctx.resolve(node.config?.agentTimeoutMs || 0));
|
|
7189
7576
|
|
|
@@ -7199,20 +7586,51 @@ registerBuiltinNodeType("agent.run_planner", {
|
|
|
7199
7586
|
// This delegates to the existing planner prompt flow
|
|
7200
7587
|
const agentPool = engine.services?.agentPool;
|
|
7201
7588
|
const plannerPrompt = engine.services?.prompts?.planner;
|
|
7589
|
+
const basePrompt = explicitPrompt || plannerPrompt || "";
|
|
7590
|
+
const fullPromptForRepoMapCheck = [basePrompt, context, plannerFeedback].filter(Boolean).join("\n\n");
|
|
7591
|
+
const promptHasRepoMap = hasRepoMapContext(fullPromptForRepoMapCheck);
|
|
7592
|
+
const repoTopologyContext = (node.config?.repoMap || repoMapQuery)
|
|
7593
|
+
&& !promptHasRepoMap
|
|
7594
|
+
? buildRepoTopologyContext({
|
|
7595
|
+
repoMap: node.config?.repoMap || ctx.data?.repoMap || null,
|
|
7596
|
+
repoMapFileLimit: node.config?.repoMapFileLimit ?? 8,
|
|
7597
|
+
repoMapQuery,
|
|
7598
|
+
query: [context, explicitPrompt, plannerPrompt].filter(Boolean).join(" "),
|
|
7599
|
+
prompt: explicitPrompt || plannerPrompt || "",
|
|
7600
|
+
userMessage: context,
|
|
7601
|
+
taskTitle: ctx.data?.taskTitle || ctx.data?.task?.title || "",
|
|
7602
|
+
taskDescription:
|
|
7603
|
+
ctx.data?.taskDescription ||
|
|
7604
|
+
ctx.data?.task?.description ||
|
|
7605
|
+
ctx.data?.task?.body ||
|
|
7606
|
+
ctx.data?.taskDetail?.description ||
|
|
7607
|
+
ctx.data?.taskInfo?.description ||
|
|
7608
|
+
"",
|
|
7609
|
+
changedFiles:
|
|
7610
|
+
(Array.isArray(ctx.data?.changedFiles) ? ctx.data.changedFiles : null) ||
|
|
7611
|
+
(Array.isArray(ctx.data?.task?.changedFiles) ? ctx.data.task.changedFiles : null) ||
|
|
7612
|
+
[],
|
|
7613
|
+
cwd: process.cwd(),
|
|
7614
|
+
repoRoot: ctx.data?.repoRoot || process.cwd(),
|
|
7615
|
+
})
|
|
7616
|
+
: "";
|
|
7202
7617
|
// Enforce strict output instructions to ensure the downstream materialize node
|
|
7203
7618
|
// can parse the planner output. The planner prompt already defines the contract,
|
|
7204
7619
|
// but we reinforce it here to prevent agents from wrapping output in prose.
|
|
7205
7620
|
const outputEnforcement =
|
|
7206
7621
|
`\n\n## CRITICAL OUTPUT REQUIREMENT\n` +
|
|
7207
7622
|
`Generate exactly ${count} new tasks.\n` +
|
|
7208
|
-
((context || plannerFeedback)
|
|
7209
|
-
? `${[
|
|
7623
|
+
((context || plannerFeedback || repoTopologyContext)
|
|
7624
|
+
? `${[
|
|
7625
|
+
context,
|
|
7626
|
+
plannerFeedback ? `Planner feedback context:\n${plannerFeedback}` : "",
|
|
7627
|
+
repoTopologyContext,
|
|
7628
|
+
].filter(Boolean).join("\n\n")}\n\n`
|
|
7210
7629
|
: "\n") +
|
|
7211
7630
|
`Your response MUST be a single fenced JSON block with shape { "tasks": [...] }.\n` +
|
|
7212
7631
|
`Do NOT include status updates, analysis notes, tool commentary, questions, or prose outside the JSON block.\n` +
|
|
7213
7632
|
`Do NOT reference or use legacy ve-kanban integration commands or scripts.\n` +
|
|
7214
7633
|
`The downstream system will parse your output as JSON — any extra text will cause task creation to fail.`;
|
|
7215
|
-
const basePrompt = explicitPrompt || plannerPrompt || "";
|
|
7216
7634
|
const promptText = basePrompt
|
|
7217
7635
|
? `${basePrompt}${outputEnforcement}`
|
|
7218
7636
|
: "";
|
|
@@ -7312,7 +7730,7 @@ registerBuiltinNodeType("agent.evidence_collect", {
|
|
|
7312
7730
|
types: { type: "array", items: { type: "string" }, default: ["png", "jpg", "json", "log", "txt"] },
|
|
7313
7731
|
},
|
|
7314
7732
|
},
|
|
7315
|
-
async execute(node, ctx) {
|
|
7733
|
+
async execute(node, ctx, engine) {
|
|
7316
7734
|
const dir = ctx.resolve(node.config?.evidenceDir || ".bosun/evidence");
|
|
7317
7735
|
if (!existsSync(dir)) {
|
|
7318
7736
|
mkdirSync(dir, { recursive: true });
|
|
@@ -7443,7 +7861,7 @@ registerBuiltinNodeType("flow.join", {
|
|
|
7443
7861
|
},
|
|
7444
7862
|
},
|
|
7445
7863
|
},
|
|
7446
|
-
async execute(node, ctx) {
|
|
7864
|
+
async execute(node, ctx, engine) {
|
|
7447
7865
|
const mode = String(ctx.resolve(node.config?.mode || "all") || "all").toLowerCase();
|
|
7448
7866
|
const includeSkipped = parseBooleanSetting(
|
|
7449
7867
|
resolveWorkflowNodeValue(node.config?.includeSkipped ?? true, ctx),
|
|
@@ -7526,7 +7944,7 @@ registerBuiltinNodeType("flow.end", {
|
|
|
7526
7944
|
},
|
|
7527
7945
|
},
|
|
7528
7946
|
},
|
|
7529
|
-
async execute(node, ctx) {
|
|
7947
|
+
async execute(node, ctx, engine) {
|
|
7530
7948
|
const rawStatus = String(ctx.resolve(node.config?.status || "completed") || "completed")
|
|
7531
7949
|
.trim()
|
|
7532
7950
|
.toLowerCase();
|
|
@@ -7620,6 +8038,54 @@ const UNIVERSAL_FLOW_NODE = {
|
|
|
7620
8038
|
...configuredInput,
|
|
7621
8039
|
}, workflowId);
|
|
7622
8040
|
const childRunOpts = makeChildWorkflowExecuteOptions(ctx, { childWorkflowId: workflowId });
|
|
8041
|
+
const trackedTaskId = String(
|
|
8042
|
+
ctx.data?.taskId ||
|
|
8043
|
+
ctx.data?.task?.id ||
|
|
8044
|
+
ctx.data?.taskDetail?.id ||
|
|
8045
|
+
ctx.data?.taskInfo?.id ||
|
|
8046
|
+
"",
|
|
8047
|
+
).trim();
|
|
8048
|
+
const trackedTaskTitle = String(
|
|
8049
|
+
ctx.data?.taskTitle ||
|
|
8050
|
+
ctx.data?.task?.title ||
|
|
8051
|
+
ctx.data?.taskDetail?.title ||
|
|
8052
|
+
ctx.data?.taskInfo?.title ||
|
|
8053
|
+
trackedTaskId,
|
|
8054
|
+
).trim();
|
|
8055
|
+
const tracker = trackedTaskId ? getSessionTracker() : null;
|
|
8056
|
+
if (tracker && trackedTaskId) {
|
|
8057
|
+
const existing = tracker.getSessionById(trackedTaskId);
|
|
8058
|
+
if (!existing) {
|
|
8059
|
+
tracker.createSession({
|
|
8060
|
+
id: trackedTaskId,
|
|
8061
|
+
type: "task",
|
|
8062
|
+
taskId: trackedTaskId,
|
|
8063
|
+
metadata: {
|
|
8064
|
+
title: trackedTaskTitle || trackedTaskId,
|
|
8065
|
+
workspaceId: String(ctx.data?.workspaceId || ctx.data?.activeWorkspace || "").trim() || undefined,
|
|
8066
|
+
workspaceDir: String(ctx.data?.worktreePath || ctx.data?.workspaceDir || "").trim() || undefined,
|
|
8067
|
+
branch:
|
|
8068
|
+
String(
|
|
8069
|
+
ctx.data?.branch ||
|
|
8070
|
+
ctx.data?.task?.branchName ||
|
|
8071
|
+
ctx.data?.taskDetail?.branchName ||
|
|
8072
|
+
ctx.data?.taskInfo?.branchName ||
|
|
8073
|
+
"",
|
|
8074
|
+
).trim() || undefined,
|
|
8075
|
+
},
|
|
8076
|
+
});
|
|
8077
|
+
} else {
|
|
8078
|
+
tracker.updateSessionStatus(trackedTaskId, "active");
|
|
8079
|
+
if (trackedTaskTitle) tracker.renameSession(trackedTaskId, trackedTaskTitle);
|
|
8080
|
+
}
|
|
8081
|
+
tracker.recordEvent(trackedTaskId, {
|
|
8082
|
+
role: "system",
|
|
8083
|
+
type: "system",
|
|
8084
|
+
content: `Delegating to workflow "${workflowId}"`,
|
|
8085
|
+
timestamp: new Date().toISOString(),
|
|
8086
|
+
_sessionType: "task",
|
|
8087
|
+
});
|
|
8088
|
+
}
|
|
7623
8089
|
|
|
7624
8090
|
if (mode === "dispatch") {
|
|
7625
8091
|
ctx.log(node.id, `Dispatching universal workflow \"${workflowId}\"`);
|
|
@@ -7632,9 +8098,29 @@ const UNIVERSAL_FLOW_NODE = {
|
|
|
7632
8098
|
dispatched
|
|
7633
8099
|
.then((childCtx) => {
|
|
7634
8100
|
const status = childCtx?.errors?.length ? "failed" : "completed";
|
|
8101
|
+
if (tracker && trackedTaskId) {
|
|
8102
|
+
tracker.updateSessionStatus(trackedTaskId, status);
|
|
8103
|
+
tracker.recordEvent(trackedTaskId, {
|
|
8104
|
+
role: status === "completed" ? "assistant" : "system",
|
|
8105
|
+
type: status === "completed" ? "agent_message" : "error",
|
|
8106
|
+
content: `Workflow "${workflowId}" ${status}`,
|
|
8107
|
+
timestamp: new Date().toISOString(),
|
|
8108
|
+
_sessionType: "task",
|
|
8109
|
+
});
|
|
8110
|
+
}
|
|
7635
8111
|
ctx.log(node.id, `Dispatched universal workflow \"${workflowId}\" finished with status=${status}`);
|
|
7636
8112
|
})
|
|
7637
8113
|
.catch((err) => {
|
|
8114
|
+
if (tracker && trackedTaskId) {
|
|
8115
|
+
tracker.updateSessionStatus(trackedTaskId, "failed");
|
|
8116
|
+
tracker.recordEvent(trackedTaskId, {
|
|
8117
|
+
role: "system",
|
|
8118
|
+
type: "error",
|
|
8119
|
+
content: `Workflow "${workflowId}" failed: ${err.message}`,
|
|
8120
|
+
timestamp: new Date().toISOString(),
|
|
8121
|
+
_sessionType: "task",
|
|
8122
|
+
});
|
|
8123
|
+
}
|
|
7638
8124
|
ctx.log(node.id, `Dispatched universal workflow \"${workflowId}\" failed: ${err.message}`, "error");
|
|
7639
8125
|
});
|
|
7640
8126
|
|
|
@@ -7659,8 +8145,20 @@ const UNIVERSAL_FLOW_NODE = {
|
|
|
7659
8145
|
workflowId,
|
|
7660
8146
|
runId: childCtx?.id || null,
|
|
7661
8147
|
status: errorCount > 0 ? "failed" : "completed",
|
|
8148
|
+
message: String(childCtx?.data?._workflowTerminalMessage || "").trim(),
|
|
8149
|
+
output: childCtx?.data?._workflowTerminalOutput,
|
|
7662
8150
|
errorCount,
|
|
7663
8151
|
};
|
|
8152
|
+
if (tracker && trackedTaskId) {
|
|
8153
|
+
tracker.updateSessionStatus(trackedTaskId, output.status);
|
|
8154
|
+
tracker.recordEvent(trackedTaskId, {
|
|
8155
|
+
role: output.status === "completed" ? "assistant" : "system",
|
|
8156
|
+
type: output.status === "completed" ? "agent_message" : "error",
|
|
8157
|
+
content: `Workflow "${workflowId}" ${output.status}${output.message ? `: ${output.message}` : ""}`,
|
|
8158
|
+
timestamp: new Date().toISOString(),
|
|
8159
|
+
_sessionType: "task",
|
|
8160
|
+
});
|
|
8161
|
+
}
|
|
7664
8162
|
if (outputVariable) ctx.data[outputVariable] = output;
|
|
7665
8163
|
return output;
|
|
7666
8164
|
},
|
|
@@ -7975,12 +8473,30 @@ registerBuiltinNodeType("action.continue_session", {
|
|
|
7975
8473
|
const prompt = ctx.resolve(node.config?.prompt || "Continue working on the current task.");
|
|
7976
8474
|
const timeout = node.config?.timeoutMs || 1800000;
|
|
7977
8475
|
const strategy = node.config?.strategy || "continue";
|
|
8476
|
+
const issueAdvisor =
|
|
8477
|
+
ctx.data?._issueAdvisor && typeof ctx.data._issueAdvisor === "object"
|
|
8478
|
+
? ctx.data._issueAdvisor
|
|
8479
|
+
: null;
|
|
8480
|
+
const dagStateSummary =
|
|
8481
|
+
ctx.data?._plannerFeedback?.dagStateSummary && typeof ctx.data._plannerFeedback.dagStateSummary === "object"
|
|
8482
|
+
? ctx.data._plannerFeedback.dagStateSummary
|
|
8483
|
+
: null;
|
|
8484
|
+
const continuationPrefix = issueAdvisor
|
|
8485
|
+
? [
|
|
8486
|
+
"Issue-advisor continuation context:",
|
|
8487
|
+
`- Recommendation: ${issueAdvisor.recommendedAction || "continue"}`,
|
|
8488
|
+
issueAdvisor.summary ? `- Summary: ${issueAdvisor.summary}` : null,
|
|
8489
|
+
issueAdvisor.nextStepGuidance ? `- Guidance: ${issueAdvisor.nextStepGuidance}` : null,
|
|
8490
|
+
dagStateSummary?.counts ? `- DAG counts: completed=${Number(dagStateSummary.counts.completed ?? 0) || 0}, failed=${Number(dagStateSummary.counts.failed ?? 0) || 0}, pending=${Number(dagStateSummary.counts.pending ?? 0) || 0}` : null,
|
|
8491
|
+
].filter(Boolean).join("\n") + "\n\n"
|
|
8492
|
+
: "";
|
|
8493
|
+
const enrichedPrompt = continuationPrefix ? `${continuationPrefix}${prompt}` : prompt;
|
|
7978
8494
|
|
|
7979
8495
|
ctx.log(node.id, `Continuing session ${sessionId} (strategy: ${strategy})`);
|
|
7980
8496
|
|
|
7981
8497
|
const agentPool = engine.services?.agentPool;
|
|
7982
8498
|
if (agentPool?.continueSession) {
|
|
7983
|
-
const result = await agentPool.continueSession(sessionId,
|
|
8499
|
+
const result = await agentPool.continueSession(sessionId, enrichedPrompt, { timeout, strategy });
|
|
7984
8500
|
|
|
7985
8501
|
// Propagate session ID for downstream chaining
|
|
7986
8502
|
const threadId = result.threadId || sessionId;
|
|
@@ -7993,12 +8509,12 @@ registerBuiltinNodeType("action.continue_session", {
|
|
|
7993
8509
|
// Fallback: use ephemeral thread with continuation context
|
|
7994
8510
|
if (agentPool?.launchEphemeralThread) {
|
|
7995
8511
|
const continuation = strategy === "retry"
|
|
7996
|
-
? `Start over on this task. Previous attempt failed.\n\n${
|
|
8512
|
+
? `Start over on this task. Previous attempt failed.\n\n${enrichedPrompt}`
|
|
7997
8513
|
: strategy === "refine"
|
|
7998
|
-
? `Refine your previous work. Specifically:\n\n${
|
|
8514
|
+
? `Refine your previous work. Specifically:\n\n${enrichedPrompt}`
|
|
7999
8515
|
: strategy === "finish_up"
|
|
8000
|
-
? `Wrap up the current task. Commit, push, and hand off PR lifecycle to Bosun. Ensure tests pass.\n\n${
|
|
8001
|
-
: `Continue where you left off.\n\n${
|
|
8516
|
+
? `Wrap up the current task. Commit, push, and hand off PR lifecycle to Bosun. Ensure tests pass.\n\n${enrichedPrompt}`
|
|
8517
|
+
: `Continue where you left off.\n\n${enrichedPrompt}`;
|
|
8002
8518
|
|
|
8003
8519
|
const result = await agentPool.launchEphemeralThread(continuation, ctx.data?.worktreePath || process.cwd(), timeout);
|
|
8004
8520
|
|
|
@@ -8089,7 +8605,7 @@ registerBuiltinNodeType("action.bosun_cli", {
|
|
|
8089
8605
|
},
|
|
8090
8606
|
required: ["subcommand"],
|
|
8091
8607
|
},
|
|
8092
|
-
async execute(node, ctx) {
|
|
8608
|
+
async execute(node, ctx, engine) {
|
|
8093
8609
|
const sub = node.config?.subcommand || "";
|
|
8094
8610
|
const args = ctx.resolve(node.config?.args || "");
|
|
8095
8611
|
const cmd = `bosun ${sub} ${args}`.trim();
|
|
@@ -8218,7 +8734,7 @@ registerBuiltinNodeType("action.bosun_tool", {
|
|
|
8218
8734
|
},
|
|
8219
8735
|
required: ["toolId"],
|
|
8220
8736
|
},
|
|
8221
|
-
async execute(node, ctx) {
|
|
8737
|
+
async execute(node, ctx, engine) {
|
|
8222
8738
|
const toolId = ctx.resolve(node.config?.toolId || "");
|
|
8223
8739
|
if (!toolId) throw new Error("action.bosun_tool: 'toolId' is required");
|
|
8224
8740
|
|
|
@@ -8915,7 +9431,7 @@ registerBuiltinNodeType("action.handle_rate_limit", {
|
|
|
8915
9431
|
strategy: { type: "string", enum: ["wait", "rotate", "skip"], default: "wait", description: "Rate limit strategy" },
|
|
8916
9432
|
},
|
|
8917
9433
|
},
|
|
8918
|
-
async execute(node, ctx) {
|
|
9434
|
+
async execute(node, ctx, engine) {
|
|
8919
9435
|
const attempt = ctx.data?._rateLimitAttempt || 0;
|
|
8920
9436
|
const maxRetries = node.config?.maxRetries || 5;
|
|
8921
9437
|
const strategy = node.config?.strategy || "wait";
|
|
@@ -9068,7 +9584,7 @@ registerBuiltinNodeType("action.refresh_worktree", {
|
|
|
9068
9584
|
},
|
|
9069
9585
|
required: ["operation"],
|
|
9070
9586
|
},
|
|
9071
|
-
async execute(node, ctx) {
|
|
9587
|
+
async execute(node, ctx, engine) {
|
|
9072
9588
|
const op = node.config?.operation || "fetch";
|
|
9073
9589
|
const cwd = ctx.resolve(node.config?.cwd || ctx.data?.worktreePath || process.cwd());
|
|
9074
9590
|
const branch = ctx.resolve(node.config?.branch || "main");
|
|
@@ -9423,7 +9939,7 @@ registerBuiltinNodeType("action.mcp_tool_call", {
|
|
|
9423
9939
|
},
|
|
9424
9940
|
required: ["server", "tool"],
|
|
9425
9941
|
},
|
|
9426
|
-
async execute(node, ctx) {
|
|
9942
|
+
async execute(node, ctx, engine) {
|
|
9427
9943
|
const serverId = ctx.resolve(node.config?.server || "");
|
|
9428
9944
|
const toolName = ctx.resolve(node.config?.tool || "");
|
|
9429
9945
|
const timeoutMs = node.config?.timeoutMs || 30000;
|
|
@@ -9518,7 +10034,7 @@ registerBuiltinNodeType("action.mcp_list_tools", {
|
|
|
9518
10034
|
},
|
|
9519
10035
|
required: ["server"],
|
|
9520
10036
|
},
|
|
9521
|
-
async execute(node, ctx) {
|
|
10037
|
+
async execute(node, ctx, engine) {
|
|
9522
10038
|
const serverId = ctx.resolve(node.config?.server || "");
|
|
9523
10039
|
const timeoutMs = node.config?.timeoutMs || 30000;
|
|
9524
10040
|
|
|
@@ -9645,7 +10161,7 @@ registerBuiltinNodeType("action.mcp_pipeline", {
|
|
|
9645
10161
|
},
|
|
9646
10162
|
required: ["steps"],
|
|
9647
10163
|
},
|
|
9648
|
-
async execute(node, ctx) {
|
|
10164
|
+
async execute(node, ctx, engine) {
|
|
9649
10165
|
const adapter = await getMcpAdapter();
|
|
9650
10166
|
const pipelineSpec = adapter.createPipelineSpec(node.config?.steps || []);
|
|
9651
10167
|
|
|
@@ -9850,7 +10366,7 @@ registerBuiltinNodeType("transform.mcp_extract", {
|
|
|
9850
10366
|
},
|
|
9851
10367
|
required: ["source", "fields"],
|
|
9852
10368
|
},
|
|
9853
|
-
async execute(node, ctx) {
|
|
10369
|
+
async execute(node, ctx, engine) {
|
|
9854
10370
|
const sourceNodeId = ctx.resolve(node.config?.source || "");
|
|
9855
10371
|
const sourceField = node.config?.sourceField || "data";
|
|
9856
10372
|
|
|
@@ -10942,6 +11458,8 @@ registerBuiltinNodeType("trigger.task_available", {
|
|
|
10942
11458
|
return {
|
|
10943
11459
|
triggered: true,
|
|
10944
11460
|
tasks: toDispatch,
|
|
11461
|
+
task: primaryTask,
|
|
11462
|
+
taskTitle: primaryTask ? pickTaskString(primaryTask.title, primaryTask.task_title) : "",
|
|
10945
11463
|
taskCount: toDispatch.length,
|
|
10946
11464
|
availableSlots: remaining,
|
|
10947
11465
|
selectedTaskId: primaryTask ? pickTaskString(primaryTask.id, primaryTask.task_id) : "",
|
|
@@ -10964,7 +11482,7 @@ registerBuiltinNodeType("condition.slot_available", {
|
|
|
10964
11482
|
baseBranch: { type: "string", description: "Base branch to check against" },
|
|
10965
11483
|
},
|
|
10966
11484
|
},
|
|
10967
|
-
async execute(node, ctx) {
|
|
11485
|
+
async execute(node, ctx, engine) {
|
|
10968
11486
|
const maxParallel = node.config?.maxParallel ?? 3;
|
|
10969
11487
|
const baseBranchLimit = node.config?.baseBranchLimit ?? 0;
|
|
10970
11488
|
const activeSlotCount = ctx.data?.activeSlotCount ?? 0;
|
|
@@ -11002,7 +11520,7 @@ registerBuiltinNodeType("action.allocate_slot", {
|
|
|
11002
11520
|
},
|
|
11003
11521
|
required: ["taskId"],
|
|
11004
11522
|
},
|
|
11005
|
-
async execute(node, ctx) {
|
|
11523
|
+
async execute(node, ctx, engine) {
|
|
11006
11524
|
const taskId = cfgOrCtx(node, ctx, "taskId");
|
|
11007
11525
|
const taskTitle = cfgOrCtx(node, ctx, "taskTitle", "(untitled)");
|
|
11008
11526
|
const branch = cfgOrCtx(node, ctx, "branch");
|
|
@@ -11056,7 +11574,7 @@ registerBuiltinNodeType("action.release_slot", {
|
|
|
11056
11574
|
taskId: { type: "string", description: "Task ID whose slot to release" },
|
|
11057
11575
|
},
|
|
11058
11576
|
},
|
|
11059
|
-
async execute(node, ctx) {
|
|
11577
|
+
async execute(node, ctx, engine) {
|
|
11060
11578
|
const taskId = cfgOrCtx(node, ctx, "taskId");
|
|
11061
11579
|
const slot = ctx.data?._allocatedSlot;
|
|
11062
11580
|
|
|
@@ -11100,7 +11618,7 @@ registerBuiltinNodeType("action.claim_task", {
|
|
|
11100
11618
|
},
|
|
11101
11619
|
required: ["taskId"],
|
|
11102
11620
|
},
|
|
11103
|
-
async execute(node, ctx) {
|
|
11621
|
+
async execute(node, ctx, engine) {
|
|
11104
11622
|
const taskId = cfgOrCtx(node, ctx, "taskId");
|
|
11105
11623
|
const taskTitle = cfgOrCtx(node, ctx, "taskTitle");
|
|
11106
11624
|
const ttlMinutes = node.config?.ttlMinutes ?? 180;
|
|
@@ -11222,7 +11740,7 @@ registerBuiltinNodeType("action.release_claim", {
|
|
|
11222
11740
|
instanceId: { type: "string", description: "Instance ID (auto-read from ctx)" },
|
|
11223
11741
|
},
|
|
11224
11742
|
},
|
|
11225
|
-
async execute(node, ctx) {
|
|
11743
|
+
async execute(node, ctx, engine) {
|
|
11226
11744
|
const taskId = cfgOrCtx(node, ctx, "taskId");
|
|
11227
11745
|
const claimToken = cfgOrCtx(node, ctx, "claimToken") || ctx.data?._claimToken || "";
|
|
11228
11746
|
const instanceId = cfgOrCtx(node, ctx, "instanceId") || ctx.data?._claimInstanceId || "";
|
|
@@ -11289,7 +11807,7 @@ registerBuiltinNodeType("action.resolve_executor", {
|
|
|
11289
11807
|
modelOverride: { type: "string", description: "Force a specific model" },
|
|
11290
11808
|
},
|
|
11291
11809
|
},
|
|
11292
|
-
async execute(node, ctx) {
|
|
11810
|
+
async execute(node, ctx, engine) {
|
|
11293
11811
|
const defaultSdk = cfgOrCtx(node, ctx, "defaultSdk", "auto");
|
|
11294
11812
|
const sdkOverride = cfgOrCtx(node, ctx, "sdkOverride");
|
|
11295
11813
|
const modelOverride = cfgOrCtx(node, ctx, "modelOverride");
|
|
@@ -11530,7 +12048,7 @@ registerBuiltinNodeType("action.acquire_worktree", {
|
|
|
11530
12048
|
},
|
|
11531
12049
|
required: ["branch", "taskId"],
|
|
11532
12050
|
},
|
|
11533
|
-
async execute(node, ctx) {
|
|
12051
|
+
async execute(node, ctx, engine) {
|
|
11534
12052
|
// Outer guard: ensure we ALWAYS return structured output with recoveryNote
|
|
11535
12053
|
// so downstream {{acquire-worktree.recoveryNote}} templates never stay literal.
|
|
11536
12054
|
let taskId, branch, repoRoot, baseBranch;
|
|
@@ -12067,7 +12585,7 @@ registerBuiltinNodeType("action.release_worktree", {
|
|
|
12067
12585
|
removeTimeout: { type: "number", default: 30000, description: "Timeout for removal (ms)" },
|
|
12068
12586
|
},
|
|
12069
12587
|
},
|
|
12070
|
-
async execute(node, ctx) {
|
|
12588
|
+
async execute(node, ctx, engine) {
|
|
12071
12589
|
const worktreePath = cfgOrCtx(node, ctx, "worktreePath");
|
|
12072
12590
|
const repoRoot = cfgOrCtx(node, ctx, "repoRoot") || process.cwd();
|
|
12073
12591
|
const taskId = cfgOrCtx(node, ctx, "taskId");
|
|
@@ -12129,7 +12647,7 @@ registerBuiltinNodeType("action.recover_worktree", {
|
|
|
12129
12647
|
removeTimeout: { type: "number", default: 30000, description: "Timeout for removal (ms)" },
|
|
12130
12648
|
},
|
|
12131
12649
|
},
|
|
12132
|
-
async execute(node, ctx) {
|
|
12650
|
+
async execute(node, ctx, engine) {
|
|
12133
12651
|
const worktreePath = cfgOrCtx(node, ctx, "worktreePath") || ctx.data?.worktreePath || "";
|
|
12134
12652
|
const repoRoot = cfgOrCtx(node, ctx, "repoRoot") || process.cwd();
|
|
12135
12653
|
const taskId = cfgOrCtx(node, ctx, "taskId") || ctx.data?.taskId || "";
|
|
@@ -12171,7 +12689,7 @@ registerBuiltinNodeType("action.sweep_task_worktrees", {
|
|
|
12171
12689
|
timeout: { type: "number", default: 15000, description: "Timeout for git worktree prune (ms)" },
|
|
12172
12690
|
},
|
|
12173
12691
|
},
|
|
12174
|
-
async execute(node, ctx) {
|
|
12692
|
+
async execute(node, ctx, engine) {
|
|
12175
12693
|
const repoRoot = cfgOrCtx(node, ctx, "repoRoot") || process.cwd();
|
|
12176
12694
|
const taskId = cfgOrCtx(node, ctx, "taskId") || ctx.data?.taskId || "";
|
|
12177
12695
|
const timeout = Number(node.config?.timeout ?? 15000);
|
|
@@ -12208,7 +12726,7 @@ const readWorkflowContractHandler = {
|
|
|
12208
12726
|
},
|
|
12209
12727
|
},
|
|
12210
12728
|
},
|
|
12211
|
-
async execute(node, ctx) {
|
|
12729
|
+
async execute(node, ctx, engine) {
|
|
12212
12730
|
const projectRoot = cfgOrCtx(node, ctx, "projectRoot")
|
|
12213
12731
|
|| cfgOrCtx(node, ctx, "worktreePath")
|
|
12214
12732
|
|| cfgOrCtx(node, ctx, "repoRoot")
|
|
@@ -12277,7 +12795,7 @@ const workflowContractValidationHandler = {
|
|
|
12277
12795
|
},
|
|
12278
12796
|
},
|
|
12279
12797
|
},
|
|
12280
|
-
async execute(node, ctx) {
|
|
12798
|
+
async execute(node, ctx, engine) {
|
|
12281
12799
|
const projectRoot = cfgOrCtx(node, ctx, "projectRoot")
|
|
12282
12800
|
|| cfgOrCtx(node, ctx, "worktreePath")
|
|
12283
12801
|
|| cfgOrCtx(node, ctx, "repoRoot")
|
|
@@ -12379,7 +12897,7 @@ registerBuiltinNodeType("action.build_task_prompt", {
|
|
|
12379
12897
|
},
|
|
12380
12898
|
required: ["taskTitle"],
|
|
12381
12899
|
},
|
|
12382
|
-
async execute(node, ctx) {
|
|
12900
|
+
async execute(node, ctx, engine) {
|
|
12383
12901
|
const taskId = cfgOrCtx(node, ctx, "taskId");
|
|
12384
12902
|
const taskTitle = cfgOrCtx(node, ctx, "taskTitle");
|
|
12385
12903
|
const taskDescription = cfgOrCtx(node, ctx, "taskDescription");
|
|
@@ -12396,6 +12914,14 @@ registerBuiltinNodeType("action.build_task_prompt", {
|
|
|
12396
12914
|
const includeMemory = node.config?.includeMemory !== false;
|
|
12397
12915
|
ctx.data._taskIncludeContext = includeComments;
|
|
12398
12916
|
const customTemplate = cfgOrCtx(node, ctx, "promptTemplate");
|
|
12917
|
+
const workflowIssueAdvisor =
|
|
12918
|
+
ctx.data?._issueAdvisor && typeof ctx.data._issueAdvisor === "object"
|
|
12919
|
+
? ctx.data._issueAdvisor
|
|
12920
|
+
: null;
|
|
12921
|
+
const workflowDagStateSummary =
|
|
12922
|
+
ctx.data?._plannerFeedback?.dagStateSummary && typeof ctx.data._plannerFeedback.dagStateSummary === "object"
|
|
12923
|
+
? ctx.data._plannerFeedback.dagStateSummary
|
|
12924
|
+
: null;
|
|
12399
12925
|
const taskPayload =
|
|
12400
12926
|
ctx.data?.task && typeof ctx.data.task === "object"
|
|
12401
12927
|
? ctx.data.task
|
|
@@ -12716,6 +13242,18 @@ registerBuiltinNodeType("action.build_task_prompt", {
|
|
|
12716
13242
|
userParts.push("");
|
|
12717
13243
|
}
|
|
12718
13244
|
|
|
13245
|
+
if (workflowIssueAdvisor || workflowDagStateSummary) {
|
|
13246
|
+
userParts.push("## Workflow Continuation Context");
|
|
13247
|
+
if (workflowIssueAdvisor?.recommendedAction) userParts.push(`- **Issue Advisor Action:** ${workflowIssueAdvisor.recommendedAction}`);
|
|
13248
|
+
if (workflowIssueAdvisor?.summary) userParts.push(`- **Issue Advisor Summary:** ${workflowIssueAdvisor.summary}`);
|
|
13249
|
+
if (workflowIssueAdvisor?.nextStepGuidance) userParts.push(`- **Next-Step Guidance:** ${workflowIssueAdvisor.nextStepGuidance}`);
|
|
13250
|
+
if (workflowDagStateSummary?.counts) {
|
|
13251
|
+
userParts.push(`- **DAG Counts:** completed=${Number(workflowDagStateSummary.counts.completed ?? 0) || 0}, failed=${Number(workflowDagStateSummary.counts.failed ?? 0) || 0}, pending=${Number(workflowDagStateSummary.counts.pending ?? 0) || 0}`);
|
|
13252
|
+
}
|
|
13253
|
+
if (workflowDagStateSummary?.revisionCount !== undefined) userParts.push(`- **DAG Revisions:** ${workflowDagStateSummary.revisionCount}`);
|
|
13254
|
+
userParts.push("");
|
|
13255
|
+
}
|
|
13256
|
+
|
|
12719
13257
|
if (includeComments) {
|
|
12720
13258
|
const taskContextBlock = buildTaskContextBlock(taskPayload);
|
|
12721
13259
|
if (taskContextBlock) {
|
|
@@ -13002,7 +13540,7 @@ registerBuiltinNodeType("action.persist_memory", {
|
|
|
13002
13540
|
},
|
|
13003
13541
|
required: ["content"],
|
|
13004
13542
|
},
|
|
13005
|
-
async execute(node, ctx) {
|
|
13543
|
+
async execute(node, ctx, engine) {
|
|
13006
13544
|
const TASK_TEMPLATE_PLACEHOLDER_RE = /^\{\{\s*[\w.-]+\s*\}\}$/;
|
|
13007
13545
|
const TASK_TEMPLATE_INLINE_PLACEHOLDER_RE = /\{\{\s*[\w.-]+\s*\}\}/g;
|
|
13008
13546
|
const normalizeString = (value) => {
|
|
@@ -13198,7 +13736,7 @@ registerBuiltinNodeType("action.auto_commit_dirty", {
|
|
|
13198
13736
|
},
|
|
13199
13737
|
required: ["worktreePath"],
|
|
13200
13738
|
},
|
|
13201
|
-
async execute(node, ctx) {
|
|
13739
|
+
async execute(node, ctx, engine) {
|
|
13202
13740
|
const worktreePath = cfgOrCtx(node, ctx, "worktreePath");
|
|
13203
13741
|
const taskId = cfgOrCtx(node, ctx, "taskId") || ctx.data?.taskId || "unknown";
|
|
13204
13742
|
|
|
@@ -13270,7 +13808,7 @@ registerBuiltinNodeType("action.detect_new_commits", {
|
|
|
13270
13808
|
baseBranch: { type: "string", description: "Base branch for diff stats" },
|
|
13271
13809
|
},
|
|
13272
13810
|
},
|
|
13273
|
-
async execute(node, ctx) {
|
|
13811
|
+
async execute(node, ctx, engine) {
|
|
13274
13812
|
const worktreePath = cfgOrCtx(node, ctx, "worktreePath");
|
|
13275
13813
|
const baseBranch = cfgOrCtx(node, ctx, "baseBranch", "origin/main");
|
|
13276
13814
|
|
|
@@ -13407,7 +13945,7 @@ registerBuiltinNodeType("action.push_branch", {
|
|
|
13407
13945
|
},
|
|
13408
13946
|
required: ["worktreePath"],
|
|
13409
13947
|
},
|
|
13410
|
-
async execute(node, ctx) {
|
|
13948
|
+
async execute(node, ctx, engine) {
|
|
13411
13949
|
const worktreePath = cfgOrCtx(node, ctx, "worktreePath");
|
|
13412
13950
|
const branch = cfgOrCtx(node, ctx, "branch", "");
|
|
13413
13951
|
const baseBranch = cfgOrCtx(node, ctx, "baseBranch", "origin/main");
|
|
@@ -14084,3 +14622,6 @@ export async function ensureWorkflowNodeTypesLoaded(options = {}) {
|
|
|
14084
14622
|
}
|
|
14085
14623
|
|
|
14086
14624
|
|
|
14625
|
+
|
|
14626
|
+
|
|
14627
|
+
|