@growthub/cli 0.12.2 → 0.13.1

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.
Files changed (31) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +50 -25
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +141 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryReviewModal.jsx +38 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +556 -248
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +242 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +52 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +1203 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +163 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxOrchestrationEditorPanel.jsx +190 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolConfirmModal.jsx +64 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolDraftPanel.jsx +376 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/dm-shared.jsx +8 -2
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +6 -1
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +2897 -934
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +10 -7
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/views/[viewId]/page.jsx +206 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +906 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/page.jsx +12 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +493 -28
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +1363 -8
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +1 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/nav-workflows.js +54 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +322 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +734 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +73 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-sidecar-routing.js +24 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +13 -4
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +96 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +122 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +1 -0
  31. package/package.json +1 -1
@@ -27,6 +27,7 @@ const SANDBOX_ENVIRONMENT_FIELDS = {
27
27
  },
28
28
  envRefs: { editor: "env-ref-multiselect" },
29
29
  lastResponse: { editor: "json-preview", readonly: true },
30
+ orchestrationGraph: { editor: "orchestration-graph", readonly: true },
30
31
  lastRunId: { editor: "readonly-text" },
31
32
  lastSourceId: { editor: "readonly-text" },
32
33
  resolverTemplateId: { editor: "readonly-text" },
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Folder workflow shortcuts — discover sandbox-environment rows for nav-folders.
3
+ * Shortcuts reference rows; they do not copy orchestrationGraph JSON.
4
+ */
5
+
6
+ import { parseOrchestrationGraph } from "./orchestration-graph.js";
7
+
8
+ const HIDDEN_SANDBOX_OBJECT_IDS = new Set(["workspace-helper-sandbox"]);
9
+
10
+ function sandboxRowId(row) {
11
+ return String(row?.Name || row?.name || row?.slug || row?.id || "").trim();
12
+ }
13
+
14
+ function listAvailableWorkflows(workspaceConfig) {
15
+ const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
16
+ const out = [];
17
+ for (const object of objects) {
18
+ if (object?.objectType !== "sandbox-environment") continue;
19
+ if (HIDDEN_SANDBOX_OBJECT_IDS.has(String(object?.id || ""))) continue;
20
+ const objectId = String(object?.id || "").trim();
21
+ if (!objectId) continue;
22
+ const objectLabel = String(object?.label || "Sandbox Environment").trim();
23
+ const rows = Array.isArray(object?.rows) ? object.rows : [];
24
+ for (const row of rows) {
25
+ const rowId = sandboxRowId(row);
26
+ if (!rowId) continue;
27
+ const graph = parseOrchestrationGraph(row?.orchestrationGraph || row?.orchestrationConfig);
28
+ const graphNodeCount = Array.isArray(graph?.nodes) ? graph.nodes.length : 0;
29
+ out.push({
30
+ objectId,
31
+ rowId,
32
+ label: rowId,
33
+ status: String(row?.lifecycleStatus || row?.status || "draft").trim(),
34
+ version: String(row?.version || "1").trim(),
35
+ graphNodeCount,
36
+ objectLabel,
37
+ });
38
+ }
39
+ }
40
+ return out.sort((a, b) => a.label.localeCompare(b.label));
41
+ }
42
+
43
+ function findSandboxRowByWorkflowRef(workspaceConfig, objectId, rowId) {
44
+ const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
45
+ const object = objects.find((o) => o?.id === objectId && o?.objectType === "sandbox-environment");
46
+ if (!object) return { object: null, row: null, rowIndex: -1 };
47
+ const wanted = String(rowId || "").trim();
48
+ const rows = Array.isArray(object.rows) ? object.rows : [];
49
+ const rowIndex = rows.findIndex((row) => sandboxRowId(row) === wanted);
50
+ if (rowIndex < 0) return { object, row: null, rowIndex: -1 };
51
+ return { object, row: rows[rowIndex], rowIndex };
52
+ }
53
+
54
+ export { listAvailableWorkflows, findSandboxRowByWorkflowRef, sandboxRowId };
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Server-side growthub-native orchestration graph execution for sandbox-run.
3
+ */
4
+
5
+ import {
6
+ applyFieldMap,
7
+ applyFilters,
8
+ extractApiRegistryCallNode,
9
+ extractInputNode,
10
+ extractTransformConfig,
11
+ normalizeJsonAtPath,
12
+ parseOrchestrationGraph,
13
+ redactSecretsFromText,
14
+ substituteVariables
15
+ } from "./orchestration-graph.js";
16
+
17
+ function normalizeMethod(value) {
18
+ const method = String(value || "GET").trim().toUpperCase();
19
+ return ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method) ? method : "GET";
20
+ }
21
+
22
+ function buildUrl(record, inputPayload) {
23
+ const baseUrl = String(record?.baseUrl || "").trim();
24
+ let endpoint = String(record?.endpoint || "").trim();
25
+ endpoint = substituteVariables(endpoint, inputPayload);
26
+ const raw = endpoint || baseUrl;
27
+ if (!raw) throw new Error("baseUrl or endpoint is required");
28
+ if (/^https?:\/\//i.test(endpoint)) return endpoint;
29
+ if (!baseUrl) throw new Error("baseUrl is required when endpoint is relative");
30
+ return `${baseUrl.replace(/\/+$/, "")}/${endpoint.replace(/^\/+/, "")}`;
31
+ }
32
+
33
+ function envKeyCandidates(ref) {
34
+ const token = String(ref || "")
35
+ .trim()
36
+ .replace(/[^a-z0-9]+/gi, "_")
37
+ .replace(/^_+|_+$/g, "")
38
+ .toUpperCase();
39
+ return Array.from(new Set([
40
+ token,
41
+ token ? `${token}_API_KEY` : "",
42
+ token ? `${token}_TOKEN` : ""
43
+ ].filter(Boolean)));
44
+ }
45
+
46
+ function readServerSecret(authRef) {
47
+ for (const key of envKeyCandidates(authRef)) {
48
+ if (process.env[key]) return { key, value: process.env[key] };
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function buildAuthHeaders(record, secretValue) {
54
+ if (!secretValue) return {};
55
+ const meta = record?.requestHeadersMetadata || {};
56
+ const headerName = String(meta.authHeaderName || record?.authHeaderName || record?.authHeader || "x-api-key").trim();
57
+ if (!headerName) return {};
58
+ const prefix = String(meta.authPrefix || record?.authPrefix || "").trim();
59
+ return { [headerName]: prefix ? `${prefix} ${secretValue}` : secretValue };
60
+ }
61
+
62
+ function findRegistryRecord(workspaceConfig, registryId) {
63
+ const id = String(registryId || "").trim();
64
+ if (!id) return null;
65
+ const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
66
+ for (const objectItem of objects) {
67
+ if (objectItem?.objectType !== "api-registry") continue;
68
+ const rows = Array.isArray(objectItem.rows) ? objectItem.rows : [];
69
+ const match = rows.find(
70
+ (r) => String(r?.integrationId || "").trim() === id
71
+ || String(r?.id || "").trim() === id
72
+ || String(r?.Name || "").trim() === id
73
+ );
74
+ if (match) return match;
75
+ }
76
+ return null;
77
+ }
78
+
79
+ function parseInputPayload(inputNode) {
80
+ const config = inputNode?.config || {};
81
+ const mode = String(config.inputMode || "manual").trim();
82
+ if (mode === "manual") {
83
+ const sample = config.samplePayload;
84
+ if (sample && typeof sample === "object") return sample;
85
+ if (typeof sample === "string" && sample.trim()) {
86
+ try {
87
+ return JSON.parse(sample);
88
+ } catch {
89
+ return {};
90
+ }
91
+ }
92
+ return {};
93
+ }
94
+ return {};
95
+ }
96
+
97
+ function transformProviderPayload(rawPayload, transformConfig) {
98
+ const config = transformConfig || {};
99
+ const rootPath = String(config.rootPath || "").trim();
100
+ let cursor = rootPath ? getValueAtPath(rawPayload, rootPath) : rawPayload;
101
+ if (cursor === undefined) cursor = rawPayload;
102
+
103
+ const fieldMap = config.fieldMap && typeof config.fieldMap === "object" ? config.fieldMap : {};
104
+ if (Object.keys(fieldMap).length) {
105
+ if (Array.isArray(cursor)) {
106
+ cursor = cursor.map((row) => applyFieldMap(row, fieldMap));
107
+ } else if (cursor && typeof cursor === "object") {
108
+ cursor = applyFieldMap(cursor, fieldMap);
109
+ }
110
+ }
111
+
112
+ if (Array.isArray(cursor)) {
113
+ const filtered = applyFilters(cursor, config.filters, config.filterMode);
114
+ const maxRows = Number(config.maxRows);
115
+ if (Number.isFinite(maxRows) && maxRows > 0) {
116
+ return filtered.slice(0, maxRows);
117
+ }
118
+ return filtered;
119
+ }
120
+
121
+ return cursor;
122
+ }
123
+
124
+ function getValueAtPath(obj, path) {
125
+ if (!path) return obj;
126
+ const parts = String(path).split(".").filter(Boolean);
127
+ let cursor = obj;
128
+ for (const part of parts) {
129
+ if (cursor == null || typeof cursor !== "object") return undefined;
130
+ cursor = cursor[part];
131
+ }
132
+ return cursor;
133
+ }
134
+
135
+ async function executeApiRegistryCall(workspaceConfig, nodeConfig, inputPayload, timeoutMs) {
136
+ const registryId = String(nodeConfig?.registryId || nodeConfig?.integrationId || "").trim();
137
+ const registryRecord = findRegistryRecord(workspaceConfig, registryId);
138
+ if (!registryRecord) {
139
+ return {
140
+ ok: false,
141
+ exitCode: 1,
142
+ durationMs: 0,
143
+ stdout: "",
144
+ stderr: "",
145
+ error: `no API Registry row for integrationId ${registryId}`,
146
+ adapterMeta: { mode: "orchestration-graph", registryId }
147
+ };
148
+ }
149
+
150
+ const merged = {
151
+ ...registryRecord,
152
+ method: nodeConfig?.method || registryRecord.method,
153
+ endpoint: nodeConfig?.endpoint || registryRecord.endpoint,
154
+ baseUrl: nodeConfig?.baseUrl || registryRecord.baseUrl,
155
+ authRef: nodeConfig?.authRef || registryRecord.authRef || registryId,
156
+ requestHeadersMetadata: {
157
+ ...(registryRecord.requestHeadersMetadata || {}),
158
+ ...(nodeConfig?.requestHeadersMetadata || {})
159
+ },
160
+ authHeaderName: nodeConfig?.requestHeadersMetadata?.authHeaderName
161
+ || registryRecord.authHeaderName
162
+ || registryRecord.authHeader,
163
+ authPrefix: nodeConfig?.requestHeadersMetadata?.authPrefix || registryRecord.authPrefix
164
+ };
165
+
166
+ let url;
167
+ try {
168
+ url = buildUrl(merged, inputPayload);
169
+ } catch (err) {
170
+ return {
171
+ ok: false,
172
+ exitCode: 1,
173
+ durationMs: 0,
174
+ stdout: "",
175
+ stderr: "",
176
+ error: err.message || "invalid URL",
177
+ adapterMeta: { mode: "orchestration-graph", registryId }
178
+ };
179
+ }
180
+
181
+ const method = normalizeMethod(merged.method);
182
+ const authRef = merged.authRef || registryId;
183
+ const secretEntry = readServerSecret(authRef);
184
+ const secret = secretEntry?.value || "";
185
+ const outboundTimeout = Math.min(Math.max(timeoutMs, 1000), 120000);
186
+ const startedAt = Date.now();
187
+ const controller = new AbortController();
188
+ const timer = setTimeout(() => controller.abort(), outboundTimeout);
189
+
190
+ const meta = nodeConfig?.requestHeadersMetadata || {};
191
+ const contentType = String(meta.contentType || "").trim() || (method === "GET" ? "" : "application/json");
192
+
193
+ let body;
194
+ const bodyTemplate = substituteVariables(String(nodeConfig?.bodyTemplate || ""), inputPayload);
195
+ if (method !== "GET" && bodyTemplate) {
196
+ try {
197
+ body = JSON.parse(bodyTemplate);
198
+ } catch {
199
+ body = bodyTemplate;
200
+ }
201
+ }
202
+
203
+ try {
204
+ const response = await fetch(url, {
205
+ method,
206
+ headers: {
207
+ accept: "application/json, text/plain;q=0.9,*/*;q=0.8",
208
+ ...(contentType ? { "content-type": contentType } : {}),
209
+ ...buildAuthHeaders(merged, secret)
210
+ },
211
+ ...(body !== undefined ? { body: typeof body === "string" ? body : JSON.stringify(body) } : {}),
212
+ signal: controller.signal
213
+ });
214
+ const durationMs = Date.now() - startedAt;
215
+ const responseContentType = response.headers.get("content-type") || "";
216
+ const payload = responseContentType.includes("application/json") ? await response.json() : await response.text();
217
+
218
+ return {
219
+ ok: response.ok,
220
+ exitCode: response.ok ? 0 : 1,
221
+ durationMs,
222
+ stdout: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2),
223
+ stderr: "",
224
+ error: response.ok ? undefined : `HTTP ${response.status}`,
225
+ rawPayload: payload,
226
+ httpStatus: response.status,
227
+ adapterMeta: {
228
+ mode: "orchestration-graph",
229
+ registryId,
230
+ url,
231
+ httpStatus: response.status,
232
+ method,
233
+ authRefSlug: authRef
234
+ }
235
+ };
236
+ } catch (error) {
237
+ const durationMs = Date.now() - startedAt;
238
+ const safeError = redactSecretsFromText(
239
+ error.name === "AbortError" ? `request timed out after ${outboundTimeout}ms` : (error.message || "fetch failed")
240
+ );
241
+ return {
242
+ ok: false,
243
+ exitCode: null,
244
+ durationMs,
245
+ stdout: "",
246
+ stderr: "",
247
+ error: safeError,
248
+ adapterMeta: { mode: "orchestration-graph", registryId, url, aborted: error.name === "AbortError" }
249
+ };
250
+ } finally {
251
+ clearTimeout(timer);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Run a growthub-native orchestration graph when present on the sandbox row.
257
+ * Returns null when the row has no executable graph (caller falls back to adapter path).
258
+ */
259
+ async function runOrchestrationGraphIfPresent({ workspaceConfig, row, timeoutMs }) {
260
+ const graph = parseOrchestrationGraph(row?.orchestrationGraph || row?.orchestrationConfig);
261
+ if (!graph || String(graph.provider || "").trim() !== "growthub-native") return null;
262
+
263
+ const apiNode = extractApiRegistryCallNode(graph);
264
+ if (!apiNode?.config) {
265
+ return {
266
+ ok: false,
267
+ exitCode: 1,
268
+ durationMs: 0,
269
+ stdout: "",
270
+ stderr: "",
271
+ error: "orchestrationGraph is missing an api-registry-call node",
272
+ adapterMeta: { mode: "orchestration-graph", provider: graph.provider }
273
+ };
274
+ }
275
+
276
+ const inputNode = extractInputNode(graph);
277
+ const inputPayload = parseInputPayload(inputNode);
278
+ const transformConfig = extractTransformConfig(graph);
279
+ const resultNode = graph.nodes?.find((n) => n?.type === "tool-result");
280
+ const successCodes = Array.isArray(resultNode?.config?.successStatusCodes)
281
+ ? resultNode.config.successStatusCodes.map(Number).filter(Number.isFinite)
282
+ : [200];
283
+
284
+ const raw = await executeApiRegistryCall(
285
+ workspaceConfig,
286
+ apiNode.config,
287
+ inputPayload,
288
+ Number(apiNode.config?.timeoutMs) || timeoutMs
289
+ );
290
+
291
+ if (raw.ok && raw.rawPayload !== undefined) {
292
+ const httpStatus = Number(raw.httpStatus);
293
+ if (successCodes.length && !successCodes.includes(httpStatus)) {
294
+ raw.ok = false;
295
+ raw.exitCode = 1;
296
+ raw.error = `HTTP ${httpStatus} is not in successStatusCodes`;
297
+ }
298
+ const transformed = transformProviderPayload(raw.rawPayload, transformConfig);
299
+ raw.stdout = typeof transformed === "string"
300
+ ? transformed
301
+ : normalizeJsonAtPath(transformed, "");
302
+ delete raw.rawPayload;
303
+ delete raw.httpStatus;
304
+ } else if (raw.error) {
305
+ raw.error = redactSecretsFromText(raw.error);
306
+ }
307
+
308
+ if (raw.stdout) {
309
+ raw.stdout = redactSecretsFromText(raw.stdout);
310
+ }
311
+
312
+ return {
313
+ ...raw,
314
+ adapterMeta: {
315
+ ...(raw.adapterMeta || {}),
316
+ orchestrationProvider: graph.provider,
317
+ orchestrationVersion: graph.version
318
+ }
319
+ };
320
+ }
321
+
322
+ export { runOrchestrationGraphIfPresent };