@tangle-network/sandbox-ui 0.113.6 → 0.114.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/WorkflowGraph-B-NsdYz_.js +1961 -0
- package/dist/assets.d.ts +214 -206
- package/dist/assets.js +1057 -908
- package/dist/auth-CWe17Hly.js +37 -0
- package/dist/auth.d.ts +2 -30
- package/dist/auth.js +2 -15
- package/dist/chat-BOvFAe6p.js +903 -0
- package/dist/chat.d.ts +2 -230
- package/dist/chat.js +2 -34
- package/dist/connectors.d.ts +52 -49
- package/dist/connectors.js +595 -643
- package/dist/dashboard-CZ3VgNPR.js +4969 -0
- package/dist/dashboard.d.ts +4 -1297
- package/dist/dashboard.js +4 -152
- package/dist/editor.d.ts +2 -1
- package/dist/editor.js +2 -31
- package/dist/files.d.ts +2 -6
- package/dist/files.js +2 -33
- package/dist/globals.css +63 -3
- package/dist/hooks.d.ts +146 -148
- package/dist/hooks.js +222 -55
- package/dist/index-CKRaj1pM.d.ts +776 -0
- package/dist/index-CXX5IpH2.d.ts +2 -0
- package/dist/index-DPePq7c8.d.ts +1340 -0
- package/dist/index-DiyLLZuJ.d.ts +230 -0
- package/dist/index-DvmZLMT1.d.ts +30 -0
- package/dist/index-I8ZLVtHd.d.ts +330 -0
- package/dist/index-PRM636Ex.d.ts +86 -0
- package/dist/index.d.ts +21 -33
- package/dist/index.js +21 -701
- package/dist/info-panel-C9cqDzOL.js +594 -0
- package/dist/integrations.d.ts +133 -139
- package/dist/integrations.js +491 -673
- package/dist/markdown.d.ts +2 -1
- package/dist/markdown.js +2 -13
- package/dist/model-I-NIxeM-.js +1250 -0
- package/dist/model-brand-B9A4H9pJ.d.ts +98 -0
- package/dist/model-brand-hMyDTUqB.js +624 -0
- package/dist/motion-DkyBRJb9.js +37 -0
- package/dist/openui.d.ts +2 -1
- package/dist/openui.js +2 -7
- package/dist/pages.d.ts +307 -300
- package/dist/pages.js +4404 -4181
- package/dist/primitives-Dh1vG6J3.js +113 -0
- package/dist/primitives.d.ts +2 -74
- package/dist/primitives.js +2 -204
- package/dist/provider-logo-DjqmConF.js +311 -0
- package/dist/run.d.ts +2 -1
- package/dist/run.js +2 -23
- package/dist/sdk-hooks.d.ts +3 -4
- package/dist/sdk-hooks.js +3 -31
- package/dist/sidecar-DgH_vQ6m.d.ts +9 -0
- package/dist/stores.d.ts +14 -15
- package/dist/stores.js +13 -81
- package/dist/styles.css +63 -3
- package/dist/template-card-L-4gmjx2.d.ts +90 -0
- package/dist/terminal-CN9ZgHRt.js +197 -0
- package/dist/terminal.d.ts +60 -60
- package/dist/terminal.js +2 -10
- package/dist/tokens.css +51 -0
- package/dist/types.d.ts +4 -11
- package/dist/types.js +0 -1
- package/dist/use-pty-session-C8cE0adT.js +669 -0
- package/dist/use-sidecar-auth-CCTJcrq7.d.ts +145 -0
- package/dist/use-sidecar-auth-DkdcufOz.js +742 -0
- package/dist/utils-DzWbktWG.js +2 -0
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +2 -37
- package/dist/workbench-BCaCKl-J.js +8347 -0
- package/dist/workbench.d.ts +2 -310
- package/dist/workbench.js +2 -44
- package/dist/workflows.d.ts +341 -339
- package/dist/workflows.js +74 -109
- package/dist/workspace-B6AtI6ld.js +2510 -0
- package/dist/workspace.d.ts +2 -761
- package/dist/workspace.js +2 -51
- package/package.json +46 -40
- package/dist/WorkflowGraph-2QOORYIR.js +0 -1722
- package/dist/chunk-2BUPSB7O.js +0 -0
- package/dist/chunk-3KBNPQQF.js +0 -2697
- package/dist/chunk-3LIXVEEE.js +0 -279
- package/dist/chunk-72UF2H5H.js +0 -707
- package/dist/chunk-AI25G5MX.js +0 -944
- package/dist/chunk-ANSC5AFC.js +0 -314
- package/dist/chunk-AZ3AWMTM.js +0 -8
- package/dist/chunk-BGVEHENC.js +0 -10
- package/dist/chunk-CZ7CSFO4.js +0 -39
- package/dist/chunk-DYXXYGS4.js +0 -4700
- package/dist/chunk-EI44GEQ5.js +0 -6
- package/dist/chunk-IACRDXER.js +0 -219
- package/dist/chunk-IOB2PW5Z.js +0 -55
- package/dist/chunk-M4K7ZGR2.js +0 -24
- package/dist/chunk-MYZAW4RX.js +0 -665
- package/dist/chunk-OMUIPJQZ.js +0 -555
- package/dist/chunk-RQZRXKMR.js +0 -398
- package/dist/chunk-SOKKTB7W.js +0 -230
- package/dist/chunk-TADGS4Q7.js +0 -1010
- package/dist/chunk-UCQNOUN3.js +0 -7964
- package/dist/chunk-ULPLRBMF.js +0 -34
- package/dist/chunk-WID73FPH.js +0 -89
- package/dist/chunk-X3YXP7FG.js +0 -38
- package/dist/logo-DiAjq759.d.ts +0 -15
- package/dist/model-brand-BNnaUKKZ.d.ts +0 -99
- package/dist/ports-list-CDIvTNnX.d.ts +0 -17
- package/dist/sdk-hooks-Djkjq4cp.d.ts +0 -145
- package/dist/template-card-Bn-Bw5xC.d.ts +0 -87
package/dist/chunk-AI25G5MX.js
DELETED
|
@@ -1,944 +0,0 @@
|
|
|
1
|
-
// src/workflows/format.ts
|
|
2
|
-
function fmtDuration(ms) {
|
|
3
|
-
if (ms === void 0 || !Number.isFinite(ms) || ms < 0) return void 0;
|
|
4
|
-
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
5
|
-
const s = ms / 1e3;
|
|
6
|
-
if (s < 60) return `${s.toFixed(1)}s`;
|
|
7
|
-
const totalSeconds = Math.round(s);
|
|
8
|
-
return `${Math.floor(totalSeconds / 60)}m${totalSeconds % 60}s`;
|
|
9
|
-
}
|
|
10
|
-
function fmtCost(usd) {
|
|
11
|
-
if (usd === void 0 || !Number.isFinite(usd) || usd < 0) return void 0;
|
|
12
|
-
if (usd === 0) return "$0";
|
|
13
|
-
return usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
|
|
14
|
-
}
|
|
15
|
-
function clampPreview(text, max = 200) {
|
|
16
|
-
if (text.length <= max) return text;
|
|
17
|
-
const lastCode = text.charCodeAt(max - 1);
|
|
18
|
-
const end = lastCode >= 55296 && lastCode <= 56319 ? max - 1 : max;
|
|
19
|
-
return `${text.slice(0, end)}\u2026`;
|
|
20
|
-
}
|
|
21
|
-
function fmtTokens(input, output) {
|
|
22
|
-
const valid = (n) => n !== void 0 && Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
23
|
-
const i = valid(input);
|
|
24
|
-
const o = valid(output);
|
|
25
|
-
if (i === void 0 && o === void 0) return void 0;
|
|
26
|
-
return `${i ?? 0}/${o ?? 0} tok`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// src/workflows/naming.ts
|
|
30
|
-
var ACRONYMS = /* @__PURE__ */ new Set([
|
|
31
|
-
"ai",
|
|
32
|
-
"api",
|
|
33
|
-
"cd",
|
|
34
|
-
"ci",
|
|
35
|
-
"cli",
|
|
36
|
-
"cpu",
|
|
37
|
-
"css",
|
|
38
|
-
"csv",
|
|
39
|
-
"db",
|
|
40
|
-
"dns",
|
|
41
|
-
"gpu",
|
|
42
|
-
"html",
|
|
43
|
-
"http",
|
|
44
|
-
"https",
|
|
45
|
-
"id",
|
|
46
|
-
"io",
|
|
47
|
-
"ip",
|
|
48
|
-
"json",
|
|
49
|
-
"llm",
|
|
50
|
-
"mcp",
|
|
51
|
-
"ml",
|
|
52
|
-
"pr",
|
|
53
|
-
"qa",
|
|
54
|
-
"sdk",
|
|
55
|
-
"sla",
|
|
56
|
-
"sql",
|
|
57
|
-
"ssh",
|
|
58
|
-
"ui",
|
|
59
|
-
"url",
|
|
60
|
-
"uuid",
|
|
61
|
-
"vm",
|
|
62
|
-
"yaml"
|
|
63
|
-
]);
|
|
64
|
-
function words(id) {
|
|
65
|
-
return id.replace(/[._\-/]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").trim().split(/\s+/).filter(Boolean);
|
|
66
|
-
}
|
|
67
|
-
function humanizeIdentifier(id, lead = "sentence") {
|
|
68
|
-
const parts = words(id);
|
|
69
|
-
if (parts.length === 0) return id;
|
|
70
|
-
return parts.map((word, i) => {
|
|
71
|
-
const lower = word.toLowerCase();
|
|
72
|
-
if (ACRONYMS.has(lower)) return lower.toUpperCase();
|
|
73
|
-
const capitalized = lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
74
|
-
if (lead === "title") return capitalized;
|
|
75
|
-
if (i > 0) return lower;
|
|
76
|
-
return lead === "lower" ? lower : capitalized;
|
|
77
|
-
}).join(" ");
|
|
78
|
-
}
|
|
79
|
-
function shortModel(model) {
|
|
80
|
-
const slash = model.lastIndexOf("/");
|
|
81
|
-
return slash === -1 ? model : model.slice(slash + 1);
|
|
82
|
-
}
|
|
83
|
-
var ACTION_PATH = /^[a-z0-9_-]+(\.[a-z0-9_-]+)+$/i;
|
|
84
|
-
function parseActionPath(path) {
|
|
85
|
-
if (!ACTION_PATH.test(path.trim())) return null;
|
|
86
|
-
const segments = path.trim().split(".");
|
|
87
|
-
const [provider, ...rest] = segments;
|
|
88
|
-
const operation = rest[rest.length - 1];
|
|
89
|
-
const resource = rest.length > 1 ? rest.slice(0, -1).join(".") : void 0;
|
|
90
|
-
return { provider, operation, resource };
|
|
91
|
-
}
|
|
92
|
-
function actionPathLabel(path) {
|
|
93
|
-
const parts = parseActionPath(path);
|
|
94
|
-
if (!parts) return path;
|
|
95
|
-
const operation = humanizeIdentifier(parts.operation, "lower");
|
|
96
|
-
return parts.resource ? `${operation}: ${parts.resource}` : operation;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// src/workflows/cron.ts
|
|
100
|
-
var WEEKDAY_NAMES = [
|
|
101
|
-
"Sunday",
|
|
102
|
-
"Monday",
|
|
103
|
-
"Tuesday",
|
|
104
|
-
"Wednesday",
|
|
105
|
-
"Thursday",
|
|
106
|
-
"Friday",
|
|
107
|
-
"Saturday"
|
|
108
|
-
];
|
|
109
|
-
function evenStep(field, period) {
|
|
110
|
-
const m = /^\*\/(\d+)$/.exec(field);
|
|
111
|
-
if (!m) return null;
|
|
112
|
-
const n = Number.parseInt(m[1], 10);
|
|
113
|
-
if (n < 1 || n >= period || period % n !== 0) return null;
|
|
114
|
-
return n;
|
|
115
|
-
}
|
|
116
|
-
function plainInt(field) {
|
|
117
|
-
if (!/^\d+$/.test(field)) return null;
|
|
118
|
-
return Number.parseInt(field, 10);
|
|
119
|
-
}
|
|
120
|
-
function timeOfDay(minute, hour) {
|
|
121
|
-
const m = plainInt(minute);
|
|
122
|
-
const h = plainInt(hour);
|
|
123
|
-
if (m === null || h === null || m > 59 || h > 23) return null;
|
|
124
|
-
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
|
125
|
-
}
|
|
126
|
-
function weekdays(field) {
|
|
127
|
-
if (field === "1-5") return "Weekdays";
|
|
128
|
-
if (field === "0,6" || field === "6,0" || field === "6-7") return "Weekends";
|
|
129
|
-
const dayName = (value) => {
|
|
130
|
-
if (value < 0 || value > 7) return null;
|
|
131
|
-
return WEEKDAY_NAMES[value === 7 ? 0 : value];
|
|
132
|
-
};
|
|
133
|
-
const single = plainInt(field);
|
|
134
|
-
if (single !== null) {
|
|
135
|
-
const name = dayName(single);
|
|
136
|
-
return name ? `${name}s` : null;
|
|
137
|
-
}
|
|
138
|
-
if (/^\d+(,\d+)+$/.test(field)) {
|
|
139
|
-
const names = field.split(",").map((d) => dayName(Number.parseInt(d, 10)));
|
|
140
|
-
if (names.some((n) => n === null)) return null;
|
|
141
|
-
const unique = [...new Set(names)];
|
|
142
|
-
return unique.map((n) => n.slice(0, 3)).join(", ");
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
function ordinal(day) {
|
|
147
|
-
const tens = day % 100;
|
|
148
|
-
if (tens >= 11 && tens <= 13) return `${day}th`;
|
|
149
|
-
switch (day % 10) {
|
|
150
|
-
case 1:
|
|
151
|
-
return `${day}st`;
|
|
152
|
-
case 2:
|
|
153
|
-
return `${day}nd`;
|
|
154
|
-
case 3:
|
|
155
|
-
return `${day}rd`;
|
|
156
|
-
default:
|
|
157
|
-
return `${day}th`;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
function describeCron(expression) {
|
|
161
|
-
const raw = expression.trim();
|
|
162
|
-
const fields = raw.split(/\s+/);
|
|
163
|
-
if (fields.length !== 5) return raw;
|
|
164
|
-
const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
|
|
165
|
-
const anyDayOfMonth = dayOfMonth === "*";
|
|
166
|
-
const anyDayOfWeek = dayOfWeek === "*";
|
|
167
|
-
const anyMonth = month === "*";
|
|
168
|
-
if (!anyMonth) return raw;
|
|
169
|
-
if (!anyDayOfMonth && !anyDayOfWeek) return raw;
|
|
170
|
-
const everyDay = anyDayOfMonth && anyDayOfWeek;
|
|
171
|
-
if (everyDay) {
|
|
172
|
-
if (minute === "*" && hour === "*") return "Every minute";
|
|
173
|
-
const everyNMinutes = evenStep(minute, 60);
|
|
174
|
-
if (everyNMinutes !== null && hour === "*") {
|
|
175
|
-
return everyNMinutes === 1 ? "Every minute" : `Every ${everyNMinutes} minutes`;
|
|
176
|
-
}
|
|
177
|
-
const everyNHours = evenStep(hour, 24);
|
|
178
|
-
const atMinute = plainInt(minute);
|
|
179
|
-
const pastTheHour = atMinute !== null && atMinute <= 59 ? `:${String(atMinute).padStart(2, "0")}` : null;
|
|
180
|
-
if (everyNHours !== null && pastTheHour) {
|
|
181
|
-
return everyNHours === 1 ? `Hourly at ${pastTheHour}` : `Every ${everyNHours} hours at ${pastTheHour}`;
|
|
182
|
-
}
|
|
183
|
-
if (hour === "*" && pastTheHour) return `Hourly at ${pastTheHour}`;
|
|
184
|
-
const time2 = timeOfDay(minute, hour);
|
|
185
|
-
return time2 ? `Daily at ${time2}` : raw;
|
|
186
|
-
}
|
|
187
|
-
const time = timeOfDay(minute, hour);
|
|
188
|
-
if (!time) return raw;
|
|
189
|
-
if (!anyDayOfWeek) {
|
|
190
|
-
const days = weekdays(dayOfWeek);
|
|
191
|
-
return days ? `${days} at ${time}` : raw;
|
|
192
|
-
}
|
|
193
|
-
const day = plainInt(dayOfMonth);
|
|
194
|
-
if (day !== null && day >= 1 && day <= 31) {
|
|
195
|
-
return `Monthly on the ${ordinal(day)} at ${time}`;
|
|
196
|
-
}
|
|
197
|
-
return raw;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// src/workflows/provider-label.ts
|
|
201
|
-
var PROVIDER_LABELS = {
|
|
202
|
-
github: "GitHub",
|
|
203
|
-
gitlab: "GitLab",
|
|
204
|
-
slack: "Slack",
|
|
205
|
-
stripe: "Stripe",
|
|
206
|
-
notion: "Notion",
|
|
207
|
-
linear: "Linear",
|
|
208
|
-
discord: "Discord"
|
|
209
|
-
};
|
|
210
|
-
function providerLabel(provider) {
|
|
211
|
-
const key = provider.toLowerCase();
|
|
212
|
-
return PROVIDER_LABELS[key] ?? humanizeIdentifier(provider, "title");
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// src/workflows/model.ts
|
|
216
|
-
import { parse as parseYaml } from "yaml";
|
|
217
|
-
var TRIGGER_NODE_ID = "trigger";
|
|
218
|
-
var TRIGGER_NODE_ID_PATTERN = new RegExp(`^${TRIGGER_NODE_ID}:(\\d+)$`);
|
|
219
|
-
function triggerNodeId(index) {
|
|
220
|
-
return index === 0 ? TRIGGER_NODE_ID : `${TRIGGER_NODE_ID}:${index}`;
|
|
221
|
-
}
|
|
222
|
-
function triggerNodeIndex(nodeId) {
|
|
223
|
-
if (nodeId === TRIGGER_NODE_ID) return 0;
|
|
224
|
-
const match = TRIGGER_NODE_ID_PATTERN.exec(nodeId);
|
|
225
|
-
return match ? Number(match[1]) : null;
|
|
226
|
-
}
|
|
227
|
-
function actionNodeId(index) {
|
|
228
|
-
return `a${index}`;
|
|
229
|
-
}
|
|
230
|
-
function branchNodeId(actionIndex, branchIndex) {
|
|
231
|
-
return `${actionNodeId(actionIndex)}-b${branchIndex}`;
|
|
232
|
-
}
|
|
233
|
-
function wfEdgeId(source, target) {
|
|
234
|
-
return `${source}->${target}`;
|
|
235
|
-
}
|
|
236
|
-
function worstSeverity(problems) {
|
|
237
|
-
if (!problems || problems.length === 0) return null;
|
|
238
|
-
return problems.every((p) => p.severity === "warning") ? "warning" : "error";
|
|
239
|
-
}
|
|
240
|
-
var NODE_W = 292;
|
|
241
|
-
var RANK_SEP = 72;
|
|
242
|
-
var CROSS_SEP = 24;
|
|
243
|
-
var EDGE_LABEL_LANE = 180;
|
|
244
|
-
var EDGE_INSERT_LANE = 44;
|
|
245
|
-
var CARD_CHROME = 20;
|
|
246
|
-
var HEADER_ROW = 34;
|
|
247
|
-
var DESCRIPTION_ROW = 40;
|
|
248
|
-
var AGENT_BODY_ROWS = 5;
|
|
249
|
-
var ACTION_OUTPUT_ROWS = 3;
|
|
250
|
-
var STRUCTURAL_OUTPUT_ROWS = 1;
|
|
251
|
-
var WELL_PROSE_LINE = 15.125;
|
|
252
|
-
var WELL_JSON_LINE = 14.4375;
|
|
253
|
-
var WELL_JSON_GAP = 2;
|
|
254
|
-
var WELL_CHROME = 39.5;
|
|
255
|
-
function wellRow(rows) {
|
|
256
|
-
const body = Math.max(
|
|
257
|
-
rows * WELL_PROSE_LINE,
|
|
258
|
-
rows * WELL_JSON_LINE + (rows - 1) * WELL_JSON_GAP
|
|
259
|
-
);
|
|
260
|
-
return Math.ceil(WELL_CHROME + body) + 3;
|
|
261
|
-
}
|
|
262
|
-
var SLIM_HEADER_ROW = 22;
|
|
263
|
-
var BODY_LINE = 15.125;
|
|
264
|
-
var AGENT_BODY_ROW = Math.ceil(8 + AGENT_BODY_ROWS * BODY_LINE);
|
|
265
|
-
var CHIP_SLOT_ROW = 22;
|
|
266
|
-
var DECISION_HEADER_ROW = 45;
|
|
267
|
-
var STRUCTURAL_KINDS = /* @__PURE__ */ new Set(["parallel", "foreach"]);
|
|
268
|
-
function isStructuralKind(kind) {
|
|
269
|
-
return kind !== void 0 && STRUCTURAL_KINDS.has(kind);
|
|
270
|
-
}
|
|
271
|
-
var OUTPUT_PREVIEW_CHARS = 240;
|
|
272
|
-
var FOOTER_ROW = 28;
|
|
273
|
-
var COMPACT_TILE = 76;
|
|
274
|
-
var COMPACT_GAP = 8;
|
|
275
|
-
var COMPACT_LABEL_W = 152;
|
|
276
|
-
var COMPACT_NODE_SIZE = {
|
|
277
|
-
width: 168,
|
|
278
|
-
height: COMPACT_TILE + COMPACT_GAP + 34
|
|
279
|
-
};
|
|
280
|
-
var COMPACT_NODE_SIZE_RUN = {
|
|
281
|
-
width: 168,
|
|
282
|
-
height: COMPACT_TILE + COMPACT_GAP + 34 + 15
|
|
283
|
-
};
|
|
284
|
-
var COMPACT_NODE_SIZE_TB = {
|
|
285
|
-
width: COMPACT_TILE + COMPACT_GAP + COMPACT_LABEL_W,
|
|
286
|
-
height: COMPACT_TILE
|
|
287
|
-
};
|
|
288
|
-
function nodeHeight(data, withRunState) {
|
|
289
|
-
if (!withRunState || data.tone === "trigger") {
|
|
290
|
-
return CARD_CHROME + HEADER_ROW + (data.description ? DESCRIPTION_ROW : 0);
|
|
291
|
-
}
|
|
292
|
-
if (data.kind === "decision") {
|
|
293
|
-
return CARD_CHROME + DECISION_HEADER_ROW + DESCRIPTION_ROW + FOOTER_ROW;
|
|
294
|
-
}
|
|
295
|
-
if (isStructuralKind(data.kind)) {
|
|
296
|
-
return CARD_CHROME + HEADER_ROW + CHIP_SLOT_ROW + FOOTER_ROW;
|
|
297
|
-
}
|
|
298
|
-
if (data.kind === "agent.run") {
|
|
299
|
-
return CARD_CHROME + SLIM_HEADER_ROW + AGENT_BODY_ROW + FOOTER_ROW;
|
|
300
|
-
}
|
|
301
|
-
return CARD_CHROME + HEADER_ROW + wellRow(ACTION_OUTPUT_ROWS) + FOOTER_ROW;
|
|
302
|
-
}
|
|
303
|
-
function geometry(compact, runMode, direction) {
|
|
304
|
-
if (compact) {
|
|
305
|
-
if (direction === "TB") {
|
|
306
|
-
return { size: COMPACT_NODE_SIZE_TB, rankSep: 34, crossSep: 24 };
|
|
307
|
-
}
|
|
308
|
-
return {
|
|
309
|
-
size: runMode ? COMPACT_NODE_SIZE_RUN : COMPACT_NODE_SIZE,
|
|
310
|
-
rankSep: 20,
|
|
311
|
-
crossSep: 20
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
return { size: void 0, rankSep: RANK_SEP, crossSep: CROSS_SEP };
|
|
315
|
-
}
|
|
316
|
-
function asRecord(v) {
|
|
317
|
-
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
318
|
-
}
|
|
319
|
-
function str(v) {
|
|
320
|
-
return typeof v === "string" && v.trim() !== "" ? v : void 0;
|
|
321
|
-
}
|
|
322
|
-
function describeText(v) {
|
|
323
|
-
const text = str(v);
|
|
324
|
-
return text ? clampPreview(text.trim(), 220) : void 0;
|
|
325
|
-
}
|
|
326
|
-
function urlHost(url) {
|
|
327
|
-
try {
|
|
328
|
-
return new URL(url).host;
|
|
329
|
-
} catch {
|
|
330
|
-
return url;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
var CONTROL_FLOW_KEYS = ["if", "retry", "onError"];
|
|
334
|
-
var CONTROL_FLOW = new Set(CONTROL_FLOW_KEYS);
|
|
335
|
-
function actionKind(rec) {
|
|
336
|
-
return Object.keys(rec).find((k) => !CONTROL_FLOW.has(k));
|
|
337
|
-
}
|
|
338
|
-
var MINTED_PROFILE_ID = /^ap_[A-Za-z0-9_-]{16}$/;
|
|
339
|
-
function agentTitle(profile) {
|
|
340
|
-
const named = str(profile);
|
|
341
|
-
if (!named || MINTED_PROFILE_ID.test(named)) return "AI Agent";
|
|
342
|
-
return humanizeIdentifier(named);
|
|
343
|
-
}
|
|
344
|
-
function describeActionBase(action) {
|
|
345
|
-
const rec = asRecord(action);
|
|
346
|
-
const kind = actionKind(rec);
|
|
347
|
-
const cfg = asRecord(kind ? rec[kind] : void 0);
|
|
348
|
-
switch (kind) {
|
|
349
|
-
case "sandbox.spawn":
|
|
350
|
-
return {
|
|
351
|
-
title: "Sandbox",
|
|
352
|
-
kind,
|
|
353
|
-
subtitle: str(cfg.template) ?? "Provision",
|
|
354
|
-
description: describeText(cfg.prompt),
|
|
355
|
-
isRoot: false,
|
|
356
|
-
tone: "action"
|
|
357
|
-
};
|
|
358
|
-
case "integration.invoke": {
|
|
359
|
-
const path = str(cfg.path);
|
|
360
|
-
const parts = path ? parseActionPath(path) : null;
|
|
361
|
-
return {
|
|
362
|
-
// The provider IS the node's identity, exactly as in n8n — its logo and
|
|
363
|
-
// its name. What it does with it goes in the subtitle.
|
|
364
|
-
title: parts ? providerLabel(parts.provider) : "Integration",
|
|
365
|
-
kind,
|
|
366
|
-
subtitle: path ? actionPathLabel(path) : void 0,
|
|
367
|
-
path,
|
|
368
|
-
provider: parts?.provider,
|
|
369
|
-
isRoot: false,
|
|
370
|
-
tone: "action"
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
case "notify": {
|
|
374
|
-
const url = str(cfg.url);
|
|
375
|
-
return {
|
|
376
|
-
title: "Notify",
|
|
377
|
-
kind,
|
|
378
|
-
subtitle: url ? urlHost(url) : void 0,
|
|
379
|
-
description: describeText(url),
|
|
380
|
-
isRoot: false,
|
|
381
|
-
tone: "action"
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
case "agent.run": {
|
|
385
|
-
const model = str(cfg.model);
|
|
386
|
-
return {
|
|
387
|
-
title: agentTitle(cfg.profile),
|
|
388
|
-
kind,
|
|
389
|
-
// The model is the agent's defining trait, so it names the step; the
|
|
390
|
-
// prompt — the long part — is the description the expanded card clamps.
|
|
391
|
-
subtitle: model ? shortModel(model) : "Agent",
|
|
392
|
-
description: describeText(cfg.prompt),
|
|
393
|
-
model,
|
|
394
|
-
isRoot: false,
|
|
395
|
-
tone: "action"
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
case "parallel": {
|
|
399
|
-
const branches = Array.isArray(cfg.branches) ? cfg.branches : [];
|
|
400
|
-
return {
|
|
401
|
-
title: "Parallel",
|
|
402
|
-
kind,
|
|
403
|
-
subtitle: `${branches.length} branch${branches.length === 1 ? "" : "es"}`,
|
|
404
|
-
badge: branches.length > 0 ? `\xD7${branches.length}` : void 0,
|
|
405
|
-
isRoot: false,
|
|
406
|
-
tone: "structural"
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
case "foreach":
|
|
410
|
-
return {
|
|
411
|
-
title: "For each",
|
|
412
|
-
kind,
|
|
413
|
-
subtitle: "Repeat per item",
|
|
414
|
-
description: typeof cfg.items === "string" ? cfg.items : "Literal list",
|
|
415
|
-
// Only fans out when a `do` template is actually present.
|
|
416
|
-
isRoot: false,
|
|
417
|
-
tone: "structural"
|
|
418
|
-
};
|
|
419
|
-
case "decision": {
|
|
420
|
-
const options = Array.isArray(cfg.options) ? cfg.options.filter((o) => typeof o === "string") : [];
|
|
421
|
-
return {
|
|
422
|
-
// A decision is the one step whose author already wrote it a human title.
|
|
423
|
-
title: str(cfg.title) ?? "Decision",
|
|
424
|
-
kind,
|
|
425
|
-
subtitle: options.length > 0 ? describeText(options.join(" / ")) : "Waits for a human",
|
|
426
|
-
description: describeText(cfg.prompt),
|
|
427
|
-
isRoot: false,
|
|
428
|
-
tone: "structural"
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
case "script.run": {
|
|
432
|
-
const connections = Array.isArray(cfg.connections) ? cfg.connections.filter((c) => typeof c === "string") : [];
|
|
433
|
-
return {
|
|
434
|
-
title: "Script",
|
|
435
|
-
kind,
|
|
436
|
-
// What the box is GRANTED is the thing that distinguishes one script
|
|
437
|
-
// step from another at a glance; the timeout is a bound, and bounds read
|
|
438
|
-
// in the detail view with the rest of the config.
|
|
439
|
-
subtitle: connections.length > 0 ? `TypeScript \xB7 ${connections.length} connection${connections.length === 1 ? "" : "s"}` : "TypeScript",
|
|
440
|
-
// The head of the module — the same role the prompt plays on an agent:
|
|
441
|
-
// the text that says what this particular step actually does.
|
|
442
|
-
description: describeText(cfg.source),
|
|
443
|
-
isRoot: false,
|
|
444
|
-
tone: "action"
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
case "sandbox.snapshot":
|
|
448
|
-
return {
|
|
449
|
-
title: "Snapshot",
|
|
450
|
-
kind,
|
|
451
|
-
// WHICH sandbox is the only thing separating one snapshot step from
|
|
452
|
-
// another, and it is usually a `${steps…}` reference — so the subtitle
|
|
453
|
-
// reads as the step whose disk is being captured.
|
|
454
|
-
subtitle: str(cfg.sandbox) ?? "Capture a sandbox",
|
|
455
|
-
isRoot: false,
|
|
456
|
-
tone: "action"
|
|
457
|
-
};
|
|
458
|
-
case "trace.analyze": {
|
|
459
|
-
const kinds = Array.isArray(cfg.kinds) ? cfg.kinds.filter((k) => typeof k === "string") : [];
|
|
460
|
-
return {
|
|
461
|
-
title: "Trace analysis",
|
|
462
|
-
kind,
|
|
463
|
-
// The analysts that run ARE the step. The model it runs them on is
|
|
464
|
-
// incidental here (unlike agent.run, where the model IS the identity),
|
|
465
|
-
// so it stays in the config for the detail view and the node keeps its
|
|
466
|
-
// own glyph rather than borrowing a lab's brand mark.
|
|
467
|
-
subtitle: kinds.length > 0 ? describeText(
|
|
468
|
-
kinds.map((k) => humanizeIdentifier(k, "lower")).join(", ")
|
|
469
|
-
) : "Default analysts",
|
|
470
|
-
description: describeText(cfg.trace),
|
|
471
|
-
isRoot: false,
|
|
472
|
-
tone: "action"
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
default:
|
|
476
|
-
return {
|
|
477
|
-
// An action kind this version doesn't model yet still gets a readable
|
|
478
|
-
// name ("agent.review" → "Agent review") rather than its raw identifier.
|
|
479
|
-
title: kind ? humanizeIdentifier(kind) : "Action",
|
|
480
|
-
kind: kind ?? "action",
|
|
481
|
-
isRoot: false,
|
|
482
|
-
tone: "action"
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
var MAX_CONFIG_DEPTH = 100;
|
|
487
|
-
function toJsonSafe(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
|
|
488
|
-
if (value === null) return null;
|
|
489
|
-
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
490
|
-
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
491
|
-
if (typeof value !== "object") return void 0;
|
|
492
|
-
if (value instanceof Date) return value.toISOString();
|
|
493
|
-
if (seen.has(value)) return "[Circular]";
|
|
494
|
-
if (depth >= MAX_CONFIG_DEPTH) return "[Max depth exceeded]";
|
|
495
|
-
seen.add(value);
|
|
496
|
-
let out;
|
|
497
|
-
if (Array.isArray(value)) {
|
|
498
|
-
out = value.map((v) => {
|
|
499
|
-
const s = toJsonSafe(v, seen, depth + 1);
|
|
500
|
-
return s === void 0 ? null : s;
|
|
501
|
-
});
|
|
502
|
-
} else {
|
|
503
|
-
const obj = {};
|
|
504
|
-
for (const [k, v] of Object.entries(value)) {
|
|
505
|
-
const s = toJsonSafe(v, seen, depth + 1);
|
|
506
|
-
if (s !== void 0) obj[k] = s;
|
|
507
|
-
}
|
|
508
|
-
out = obj;
|
|
509
|
-
}
|
|
510
|
-
seen.delete(value);
|
|
511
|
-
return out;
|
|
512
|
-
}
|
|
513
|
-
function withConfig(base, cfg) {
|
|
514
|
-
return Object.keys(cfg).length > 0 ? { ...base, config: toJsonSafe(cfg) } : base;
|
|
515
|
-
}
|
|
516
|
-
function describeAction(action) {
|
|
517
|
-
const rec = asRecord(action);
|
|
518
|
-
const kind = actionKind(rec);
|
|
519
|
-
const base = describeActionBase(action);
|
|
520
|
-
const config = toJsonSafe(asRecord(kind ? rec[kind] : {}));
|
|
521
|
-
for (const key of CONTROL_FLOW_KEYS) {
|
|
522
|
-
if (rec[key] !== void 0) config[key] = toJsonSafe(rec[key]);
|
|
523
|
-
}
|
|
524
|
-
return Object.keys(config).length > 0 ? { ...base, config } : base;
|
|
525
|
-
}
|
|
526
|
-
function describeScope(scope) {
|
|
527
|
-
const rec = asRecord(scope);
|
|
528
|
-
const parts = [];
|
|
529
|
-
for (const [dimension, raw] of Object.entries(rec).sort(
|
|
530
|
-
([a], [b]) => a < b ? -1 : a > b ? 1 : 0
|
|
531
|
-
)) {
|
|
532
|
-
const selection = asRecord(raw);
|
|
533
|
-
const except = Array.isArray(selection.except) ? [
|
|
534
|
-
...new Set(
|
|
535
|
-
selection.except.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => entry !== "")
|
|
536
|
-
)
|
|
537
|
-
] : [];
|
|
538
|
-
if (except.length === 0) {
|
|
539
|
-
if (selection.default === "exclude") parts.push(`${dimension}: none`);
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
const named = except.length <= 2 ? except.join(", ") : `${except.length} selected`;
|
|
543
|
-
parts.push(
|
|
544
|
-
selection.default === "exclude" ? `${dimension}: ${named}` : `${dimension}: all but ${named}`
|
|
545
|
-
);
|
|
546
|
-
}
|
|
547
|
-
return parts.length > 0 ? parts.join(" \xB7 ") : void 0;
|
|
548
|
-
}
|
|
549
|
-
function describeTrigger(on) {
|
|
550
|
-
const rec = asRecord(on);
|
|
551
|
-
if (rec.provider_event) {
|
|
552
|
-
const ev = asRecord(rec.provider_event);
|
|
553
|
-
const connection = str(ev.connection);
|
|
554
|
-
const event = str(ev.event);
|
|
555
|
-
const actions = Array.isArray(ev.actions) ? ev.actions.filter((a) => typeof a === "string") : [];
|
|
556
|
-
const repo = str(ev.repo);
|
|
557
|
-
const description = describeText(
|
|
558
|
-
[
|
|
559
|
-
actions.length > 0 ? actions.join(", ") : void 0,
|
|
560
|
-
repo,
|
|
561
|
-
describeScope(ev.scope)
|
|
562
|
-
].filter(Boolean).join(" \xB7 ")
|
|
563
|
-
);
|
|
564
|
-
return withConfig(
|
|
565
|
-
{
|
|
566
|
-
title: connection ? providerLabel(connection) : "Trigger",
|
|
567
|
-
kind: "provider_event",
|
|
568
|
-
subtitle: event ? `On ${humanizeIdentifier(event, "lower")}` : "On an event",
|
|
569
|
-
description,
|
|
570
|
-
provider: connection,
|
|
571
|
-
isRoot: true,
|
|
572
|
-
tone: "trigger"
|
|
573
|
-
},
|
|
574
|
-
ev
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
if (rec.schedule) {
|
|
578
|
-
const sch = asRecord(rec.schedule);
|
|
579
|
-
const cron = str(sch.cron);
|
|
580
|
-
const tz = str(sch.timezone);
|
|
581
|
-
return withConfig(
|
|
582
|
-
{
|
|
583
|
-
title: "Schedule",
|
|
584
|
-
kind: "schedule",
|
|
585
|
-
// The cron in English; the expression itself stays in the description (and
|
|
586
|
-
// in full in the config) so the exact timetable is never hidden.
|
|
587
|
-
subtitle: cron ? describeCron(cron) : "On a timetable",
|
|
588
|
-
description: cron ? tz ? `${cron} \xB7 ${tz}` : cron : void 0,
|
|
589
|
-
isRoot: true,
|
|
590
|
-
tone: "trigger"
|
|
591
|
-
},
|
|
592
|
-
sch
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
if (rec.webhook !== void 0) {
|
|
596
|
-
return withConfig(
|
|
597
|
-
{
|
|
598
|
-
title: "Webhook",
|
|
599
|
-
kind: "webhook",
|
|
600
|
-
subtitle: "On an inbound POST",
|
|
601
|
-
isRoot: true,
|
|
602
|
-
tone: "trigger"
|
|
603
|
-
},
|
|
604
|
-
asRecord(rec.webhook)
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
const [kind] = Object.keys(rec);
|
|
608
|
-
return withConfig(
|
|
609
|
-
{
|
|
610
|
-
title: "Trigger",
|
|
611
|
-
kind: "trigger",
|
|
612
|
-
subtitle: kind ? humanizeIdentifier(kind) : void 0,
|
|
613
|
-
isRoot: true,
|
|
614
|
-
tone: "trigger"
|
|
615
|
-
},
|
|
616
|
-
asRecord(kind ? rec[kind] : void 0)
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
var WRAP_TARGET_ASPECT = 742 / 480;
|
|
620
|
-
var WRAP_ASPECT_TRIGGER = 4.2;
|
|
621
|
-
var WRAP_ROW_GAP = 28;
|
|
622
|
-
function foldColumns(count, cellW, cellH, colSep, rowSep) {
|
|
623
|
-
let best = count;
|
|
624
|
-
let bestError = Number.POSITIVE_INFINITY;
|
|
625
|
-
for (let cols = 1; cols <= count; cols++) {
|
|
626
|
-
const rows = Math.ceil(count / cols);
|
|
627
|
-
const width = cols * cellW + (cols - 1) * colSep;
|
|
628
|
-
const height = rows * cellH + (rows - 1) * rowSep;
|
|
629
|
-
const error = Math.abs(Math.log(width / height / WRAP_TARGET_ASPECT));
|
|
630
|
-
if (error < bestError) {
|
|
631
|
-
bestError = error;
|
|
632
|
-
best = cols;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return best;
|
|
636
|
-
}
|
|
637
|
-
function layoutSerpentine(nodes, edges, colSep, laneSep) {
|
|
638
|
-
const rowSep = Math.max(laneSep, WRAP_ROW_GAP);
|
|
639
|
-
const byRank = /* @__PURE__ */ new Map();
|
|
640
|
-
for (const n of nodes) {
|
|
641
|
-
const arr = byRank.get(n.rank);
|
|
642
|
-
if (arr) arr.push(n);
|
|
643
|
-
else byRank.set(n.rank, [n]);
|
|
644
|
-
}
|
|
645
|
-
const ranks = [...byRank.keys()].sort((a, b) => a - b);
|
|
646
|
-
if (ranks.some((r) => byRank.get(r).length !== 1)) return null;
|
|
647
|
-
const rankOf = new Map(nodes.map((n) => [n.id, n.rank]));
|
|
648
|
-
const joinsNextLayer = (e) => {
|
|
649
|
-
const from = rankOf.get(e.source);
|
|
650
|
-
const to = rankOf.get(e.target);
|
|
651
|
-
return from !== void 0 && to !== void 0 && to === from + 1;
|
|
652
|
-
};
|
|
653
|
-
if (!edges.every(joinsNextLayer)) return null;
|
|
654
|
-
const ordered = ranks.map((r) => byRank.get(r)[0]);
|
|
655
|
-
const count = ordered.length;
|
|
656
|
-
const cellW = Math.max(...ordered.map((n) => n.width));
|
|
657
|
-
const cellH = Math.max(...ordered.map((n) => n.height));
|
|
658
|
-
const lineAspect = (count * cellW + (count - 1) * colSep) / cellH;
|
|
659
|
-
if (lineAspect <= WRAP_TARGET_ASPECT * WRAP_ASPECT_TRIGGER) return null;
|
|
660
|
-
const cols = foldColumns(count, cellW, cellH, colSep, rowSep);
|
|
661
|
-
if (cols >= count) return null;
|
|
662
|
-
return ordered.map((node, i) => {
|
|
663
|
-
const row = Math.floor(i / cols);
|
|
664
|
-
const indexInRow = i % cols;
|
|
665
|
-
const leftToRight = row % 2 === 0;
|
|
666
|
-
const col = leftToRight ? indexInRow : cols - 1 - indexInRow;
|
|
667
|
-
return {
|
|
668
|
-
id: node.id,
|
|
669
|
-
position: {
|
|
670
|
-
x: col * (cellW + colSep) + (cellW - node.width) / 2,
|
|
671
|
-
y: row * (cellH + rowSep) + (cellH - node.height) / 2
|
|
672
|
-
},
|
|
673
|
-
width: node.width,
|
|
674
|
-
height: node.height,
|
|
675
|
-
data: node.data,
|
|
676
|
-
// Every node leaves the way its row travels and is entered from behind —
|
|
677
|
-
// including the one that turns the corner. Mirroring puts the last cell
|
|
678
|
-
// of a row directly above the first cell of the next, so a turn is
|
|
679
|
-
// source-right into target-right (or left into left): the renderer draws
|
|
680
|
-
// it as a bracket in the margin beside the column. Routing it through the
|
|
681
|
-
// BOTTOM instead would have to start below the name that sits under the
|
|
682
|
-
// tile, leaving the arrow floating clear of the node it comes from.
|
|
683
|
-
targetSide: leftToRight ? "left" : "right",
|
|
684
|
-
sourceSide: leftToRight ? "right" : "left"
|
|
685
|
-
};
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
function layoutLayers(nodes, direction, rankSep, crossSep) {
|
|
689
|
-
const isLR = direction === "LR";
|
|
690
|
-
const mainSize = (n) => isLR ? n.width : n.height;
|
|
691
|
-
const crossSize = (n) => isLR ? n.height : n.width;
|
|
692
|
-
const byRank = /* @__PURE__ */ new Map();
|
|
693
|
-
for (const n of nodes) {
|
|
694
|
-
const arr = byRank.get(n.rank);
|
|
695
|
-
if (arr) arr.push(n);
|
|
696
|
-
else byRank.set(n.rank, [n]);
|
|
697
|
-
}
|
|
698
|
-
const ranks = [...byRank.keys()].sort((a, b) => a - b);
|
|
699
|
-
const mainStart = /* @__PURE__ */ new Map();
|
|
700
|
-
let cursor = 0;
|
|
701
|
-
for (const r of ranks) {
|
|
702
|
-
mainStart.set(r, cursor);
|
|
703
|
-
cursor += Math.max(...byRank.get(r).map(mainSize)) + rankSep;
|
|
704
|
-
}
|
|
705
|
-
const out = [];
|
|
706
|
-
for (const r of ranks) {
|
|
707
|
-
const layer = byRank.get(r);
|
|
708
|
-
const extent = Math.max(...layer.map(mainSize));
|
|
709
|
-
const span = layer.reduce((s, n) => s + crossSize(n), 0) + crossSep * (layer.length - 1);
|
|
710
|
-
let cross = -span / 2;
|
|
711
|
-
for (const n of layer) {
|
|
712
|
-
const main = mainStart.get(r) + (extent - mainSize(n)) / 2;
|
|
713
|
-
out.push({
|
|
714
|
-
id: n.id,
|
|
715
|
-
position: isLR ? { x: main, y: cross } : { x: cross, y: main },
|
|
716
|
-
width: n.width,
|
|
717
|
-
height: n.height,
|
|
718
|
-
data: n.data
|
|
719
|
-
});
|
|
720
|
-
cross += crossSize(n) + crossSep;
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
const minX = Math.min(...out.map((n) => n.position.x));
|
|
724
|
-
const minY = Math.min(...out.map((n) => n.position.y));
|
|
725
|
-
for (const n of out) {
|
|
726
|
-
n.position = { x: n.position.x - minX, y: n.position.y - minY };
|
|
727
|
-
}
|
|
728
|
-
return out;
|
|
729
|
-
}
|
|
730
|
-
function classifyBackEdges(orderedIds, edges) {
|
|
731
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
732
|
-
const hasIncoming = /* @__PURE__ */ new Set();
|
|
733
|
-
for (const e of edges) {
|
|
734
|
-
const list = outgoing.get(e.source);
|
|
735
|
-
if (list) list.push(e);
|
|
736
|
-
else outgoing.set(e.source, [e]);
|
|
737
|
-
hasIncoming.add(e.target);
|
|
738
|
-
}
|
|
739
|
-
const walkOrder = [
|
|
740
|
-
...orderedIds.filter((id) => !hasIncoming.has(id)),
|
|
741
|
-
...orderedIds.filter((id) => hasIncoming.has(id))
|
|
742
|
-
];
|
|
743
|
-
const back = /* @__PURE__ */ new Set();
|
|
744
|
-
const color = new Map(orderedIds.map((id) => [id, 0]));
|
|
745
|
-
for (const start of walkOrder) {
|
|
746
|
-
if (color.get(start) !== 0) continue;
|
|
747
|
-
color.set(start, 1);
|
|
748
|
-
const stack = [{ id: start, next: 0 }];
|
|
749
|
-
while (stack.length > 0) {
|
|
750
|
-
const frame = stack[stack.length - 1];
|
|
751
|
-
const out = outgoing.get(frame.id);
|
|
752
|
-
if (!out || frame.next >= out.length) {
|
|
753
|
-
color.set(frame.id, 2);
|
|
754
|
-
stack.pop();
|
|
755
|
-
continue;
|
|
756
|
-
}
|
|
757
|
-
const edge = out[frame.next];
|
|
758
|
-
frame.next += 1;
|
|
759
|
-
const target = color.get(edge.target);
|
|
760
|
-
if (target === 1) {
|
|
761
|
-
back.add(edge.id);
|
|
762
|
-
} else if (target === 0) {
|
|
763
|
-
color.set(edge.target, 1);
|
|
764
|
-
stack.push({ id: edge.target, next: 0 });
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return back;
|
|
769
|
-
}
|
|
770
|
-
function rankByTopology(orderedIds, edges, backEdgeIds) {
|
|
771
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
772
|
-
const indegree = new Map(orderedIds.map((id) => [id, 0]));
|
|
773
|
-
for (const e of edges) {
|
|
774
|
-
if (backEdgeIds.has(e.id)) continue;
|
|
775
|
-
const list = outgoing.get(e.source);
|
|
776
|
-
if (list) list.push(e);
|
|
777
|
-
else outgoing.set(e.source, [e]);
|
|
778
|
-
indegree.set(e.target, (indegree.get(e.target) ?? 0) + 1);
|
|
779
|
-
}
|
|
780
|
-
const rank = new Map(orderedIds.map((id) => [id, 0]));
|
|
781
|
-
const queue = orderedIds.filter((id) => indegree.get(id) === 0);
|
|
782
|
-
for (let head = 0; head < queue.length; head += 1) {
|
|
783
|
-
const id = queue[head];
|
|
784
|
-
for (const e of outgoing.get(id) ?? []) {
|
|
785
|
-
rank.set(e.target, Math.max(rank.get(e.target) ?? 0, (rank.get(id) ?? 0) + 1));
|
|
786
|
-
const remaining = (indegree.get(e.target) ?? 0) - 1;
|
|
787
|
-
indegree.set(e.target, remaining);
|
|
788
|
-
if (remaining === 0) queue.push(e.target);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
return rank;
|
|
792
|
-
}
|
|
793
|
-
function buildWorkflowGraph(yaml, options) {
|
|
794
|
-
const reserveRunState = options?.reserveRunState ?? false;
|
|
795
|
-
const direction = options?.direction ?? "LR";
|
|
796
|
-
const compact = options?.compact ?? false;
|
|
797
|
-
const declared = options?.edges;
|
|
798
|
-
if (!yaml || yaml.trim() === "") {
|
|
799
|
-
return { nodes: [], edges: [], error: "No definition" };
|
|
800
|
-
}
|
|
801
|
-
let parsed;
|
|
802
|
-
try {
|
|
803
|
-
parsed = parseYaml(yaml);
|
|
804
|
-
} catch {
|
|
805
|
-
return { nodes: [], edges: [], error: "Invalid YAML" };
|
|
806
|
-
}
|
|
807
|
-
const def = asRecord(parsed);
|
|
808
|
-
const actions = Array.isArray(def.do) ? def.do : [];
|
|
809
|
-
const triggers = !def.on ? [] : Array.isArray(def.on) ? def.on : [def.on];
|
|
810
|
-
if (triggers.length === 0 && actions.length === 0) {
|
|
811
|
-
return { nodes: [], edges: [], error: "Empty workflow" };
|
|
812
|
-
}
|
|
813
|
-
const logical = [];
|
|
814
|
-
const edges = [];
|
|
815
|
-
const geo = geometry(compact, reserveRunState, direction);
|
|
816
|
-
const addNode = (id, data, rank2, withRunState) => {
|
|
817
|
-
const dims = geo.size ?? {
|
|
818
|
-
width: NODE_W,
|
|
819
|
-
height: nodeHeight(data, withRunState)
|
|
820
|
-
};
|
|
821
|
-
logical.push({ id, data, width: dims.width, height: dims.height, rank: rank2 });
|
|
822
|
-
};
|
|
823
|
-
const edgeIds = /* @__PURE__ */ new Set();
|
|
824
|
-
const addEdge = (source, target, kind, whenLabel) => {
|
|
825
|
-
const id = wfEdgeId(source, target);
|
|
826
|
-
if (edgeIds.has(id)) return;
|
|
827
|
-
edgeIds.add(id);
|
|
828
|
-
edges.push({
|
|
829
|
-
id,
|
|
830
|
-
source,
|
|
831
|
-
target,
|
|
832
|
-
kind,
|
|
833
|
-
...whenLabel ? { whenLabel } : {}
|
|
834
|
-
});
|
|
835
|
-
};
|
|
836
|
-
let rank = 0;
|
|
837
|
-
let prevExits = [];
|
|
838
|
-
const branchLeafIds = /* @__PURE__ */ new Set();
|
|
839
|
-
const triggerIds = triggers.map((_, i) => triggerNodeId(i));
|
|
840
|
-
triggers.forEach((trigger, i) => {
|
|
841
|
-
addNode(triggerNodeId(i), describeTrigger(trigger), rank, false);
|
|
842
|
-
});
|
|
843
|
-
if (triggers.length > 0) {
|
|
844
|
-
prevExits = [...triggerIds];
|
|
845
|
-
rank += 1;
|
|
846
|
-
}
|
|
847
|
-
actions.forEach((action, i) => {
|
|
848
|
-
const id = actionNodeId(i);
|
|
849
|
-
const data = describeAction(action);
|
|
850
|
-
if (i === 0 && triggers.length === 0) data.isRoot = true;
|
|
851
|
-
addNode(id, data, rank, reserveRunState);
|
|
852
|
-
if (!declared) {
|
|
853
|
-
for (const from of prevExits) {
|
|
854
|
-
addEdge(from, id, branchLeafIds.has(from) ? "join" : "spine");
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
const rec = asRecord(action);
|
|
858
|
-
const children = "parallel" in rec ? Array.isArray(asRecord(rec.parallel).branches) ? asRecord(rec.parallel).branches : [] : "foreach" in rec ? (
|
|
859
|
-
// A foreach with no `do` template has no child to render.
|
|
860
|
-
[asRecord(rec.foreach).do].filter(Boolean)
|
|
861
|
-
) : [];
|
|
862
|
-
if (children.length > 0) {
|
|
863
|
-
const branchRank = rank + 1;
|
|
864
|
-
const branchIds = [];
|
|
865
|
-
children.forEach((child, j) => {
|
|
866
|
-
const cid = branchNodeId(i, j);
|
|
867
|
-
addNode(cid, describeAction(child), branchRank, reserveRunState);
|
|
868
|
-
addEdge(id, cid, "fork");
|
|
869
|
-
branchIds.push(cid);
|
|
870
|
-
branchLeafIds.add(cid);
|
|
871
|
-
});
|
|
872
|
-
prevExits = branchIds;
|
|
873
|
-
rank = branchRank + 1;
|
|
874
|
-
} else {
|
|
875
|
-
prevExits = [id];
|
|
876
|
-
rank += 1;
|
|
877
|
-
}
|
|
878
|
-
});
|
|
879
|
-
if (declared) {
|
|
880
|
-
const known = new Set(logical.map((n) => n.id));
|
|
881
|
-
for (const spec of declared) {
|
|
882
|
-
const missing = !known.has(spec.from) ? spec.from : !known.has(spec.to) ? spec.to : null;
|
|
883
|
-
if (missing !== null) {
|
|
884
|
-
return {
|
|
885
|
-
nodes: [],
|
|
886
|
-
edges: [],
|
|
887
|
-
error: `Declared topology names "${missing}", which this definition has no step for`
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
addEdge(spec.from, spec.to, "spine", spec.whenLabel);
|
|
891
|
-
}
|
|
892
|
-
const rootward = new Set(edges.map((e) => e.target));
|
|
893
|
-
for (const n of logical) {
|
|
894
|
-
if (triggerNodeIndex(n.id) !== null) continue;
|
|
895
|
-
if (rootward.has(n.id)) continue;
|
|
896
|
-
for (const triggerId of triggerIds) addEdge(triggerId, n.id, "spine");
|
|
897
|
-
}
|
|
898
|
-
const orderedIds = logical.map((n) => n.id);
|
|
899
|
-
const backEdgeIds = classifyBackEdges(orderedIds, edges);
|
|
900
|
-
for (const e of edges) {
|
|
901
|
-
if (backEdgeIds.has(e.id)) e.backEdge = true;
|
|
902
|
-
}
|
|
903
|
-
const rankById = rankByTopology(orderedIds, edges, backEdgeIds);
|
|
904
|
-
const incoming = new Set(edges.map((e) => e.target));
|
|
905
|
-
for (const n of logical) {
|
|
906
|
-
n.rank = rankById.get(n.id) ?? 0;
|
|
907
|
-
n.data = { ...n.data, isRoot: !incoming.has(n.id) };
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
const labelled = edges.some(
|
|
911
|
-
(e) => e.whenLabel !== void 0 || e.backEdge === true
|
|
912
|
-
);
|
|
913
|
-
const rankSep = labelled ? Math.max(geo.rankSep, EDGE_LABEL_LANE) : options?.reserveEdgeInsert ? Math.max(geo.rankSep, EDGE_INSERT_LANE) : geo.rankSep;
|
|
914
|
-
const folded = options?.wrap && direction === "LR" && !labelled ? layoutSerpentine(logical, edges, rankSep, geo.crossSep) : null;
|
|
915
|
-
return {
|
|
916
|
-
nodes: folded ?? layoutLayers(logical, direction, rankSep, geo.crossSep),
|
|
917
|
-
edges,
|
|
918
|
-
error: null
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
export {
|
|
923
|
-
fmtDuration,
|
|
924
|
-
fmtCost,
|
|
925
|
-
clampPreview,
|
|
926
|
-
fmtTokens,
|
|
927
|
-
shortModel,
|
|
928
|
-
providerLabel,
|
|
929
|
-
TRIGGER_NODE_ID,
|
|
930
|
-
triggerNodeId,
|
|
931
|
-
triggerNodeIndex,
|
|
932
|
-
actionNodeId,
|
|
933
|
-
branchNodeId,
|
|
934
|
-
wfEdgeId,
|
|
935
|
-
worstSeverity,
|
|
936
|
-
AGENT_BODY_ROWS,
|
|
937
|
-
ACTION_OUTPUT_ROWS,
|
|
938
|
-
STRUCTURAL_OUTPUT_ROWS,
|
|
939
|
-
isStructuralKind,
|
|
940
|
-
OUTPUT_PREVIEW_CHARS,
|
|
941
|
-
COMPACT_TILE,
|
|
942
|
-
COMPACT_NODE_SIZE,
|
|
943
|
-
buildWorkflowGraph
|
|
944
|
-
};
|