@growthub/cli 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) 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 +522 -35
  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/page.jsx +6 -1
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +1062 -2
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +10 -7
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +906 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/page.jsx +12 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +492 -28
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +114 -30
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +1 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/nav-workflows.js +54 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +322 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +734 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +73 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-sidecar-routing.js +24 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +2 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +21 -1
  27. package/package.json +1 -1
@@ -0,0 +1,376 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { ArrowLeft } from "lucide-react";
5
+ import {
6
+ addCanonicalNodeToGraph,
7
+ buildBlankOrchestrationGraphShell,
8
+ buildDefaultOrchestrationGraphFromRegistry,
9
+ getNextCanonicalNodeId,
10
+ isApiRegistryTestSuccessful,
11
+ getOrchestrationGraphUiState,
12
+ parseOrchestrationGraph,
13
+ serializeOrchestrationGraph,
14
+ updateGraphNode,
15
+ validateOrchestrationGraph
16
+ } from "@/lib/orchestration-graph";
17
+ import { resolveConnectorAction } from "@/lib/orchestration-sidecar-routing";
18
+ import { OrchestrationGraphCanvas } from "./OrchestrationGraphCanvas.jsx";
19
+ import { OrchestrationGraphEmptyCanvas } from "./OrchestrationGraphEmptyCanvas.jsx";
20
+ import { OrchestrationNodeConfigPanel } from "./OrchestrationNodeConfigPanel.jsx";
21
+
22
+ export function SandboxToolDraftPanel({
23
+ registryRow,
24
+ draftOptions,
25
+ onDraftChange,
26
+ onRequestConfirm,
27
+ onCancel,
28
+ disabled
29
+ }) {
30
+ const integrationId = String(registryRow?.integrationId || "").trim();
31
+ const registryName = String(registryRow?.Name || integrationId).trim();
32
+ const defaultName = registryRow?.Name
33
+ ? `${String(registryRow.Name).trim()} Tool`
34
+ : `${integrationId} Tool`;
35
+
36
+ const [name, setName] = useState(draftOptions?.name || defaultName);
37
+ const [description, setDescription] = useState(draftOptions?.description || String(registryRow?.description || "").trim());
38
+ const [runLocality, setRunLocality] = useState(draftOptions?.runLocality || "local");
39
+ const [adapter, setAdapter] = useState(draftOptions?.adapter || "local-process");
40
+ const [authRef, setAuthRef] = useState(draftOptions?.authRef || String(registryRow?.authRef || integrationId).trim());
41
+ const [envRefs, setEnvRefs] = useState(draftOptions?.envRefs || "");
42
+ const [networkAllow, setNetworkAllow] = useState(Boolean(draftOptions?.networkAllow));
43
+ const [timeoutMs, setTimeoutMs] = useState(String(draftOptions?.timeoutMs || "30000"));
44
+ const [rootPath, setRootPath] = useState(draftOptions?.rootPath || "data");
45
+ const [instructions, setInstructions] = useState(draftOptions?.instructions || "");
46
+ const [agentHost, setAgentHost] = useState(draftOptions?.agentHost || "");
47
+ const [schedulerRegistryId, setSchedulerRegistryId] = useState(
48
+ draftOptions?.schedulerRegistryId || (draftOptions?.runLocality === "serverless" ? integrationId : "")
49
+ );
50
+ const [selectedNodeId, setSelectedNodeId] = useState("input");
51
+ const [configTab, setConfigTab] = useState("node");
52
+ const [graphError, setGraphError] = useState("");
53
+ const [orchestrationGraph, setOrchestrationGraph] = useState(() => {
54
+ if (draftOptions?.orchestrationGraph) {
55
+ return parseOrchestrationGraph(draftOptions.orchestrationGraph);
56
+ }
57
+ return null;
58
+ });
59
+
60
+ const registryKey = `${integrationId}:${String(registryRow?.endpoint || "")}:${String(registryRow?.method || "")}`;
61
+ const graphUiState = getOrchestrationGraphUiState(orchestrationGraph);
62
+ const graphUnset = graphUiState === "unset";
63
+ const graphBlankShell = graphUiState === "blank-shell";
64
+ const nextNodeId = useMemo(
65
+ () => (orchestrationGraph ? getNextCanonicalNodeId(orchestrationGraph) : "input"),
66
+ [orchestrationGraph]
67
+ );
68
+
69
+ useEffect(() => {
70
+ if (graphUnset || graphBlankShell) return;
71
+ setOrchestrationGraph((current) => {
72
+ const base = buildDefaultOrchestrationGraphFromRegistry(registryRow, {
73
+ label: registryName,
74
+ authRef,
75
+ rootPath
76
+ });
77
+ const parsed = parseOrchestrationGraph(current) || current;
78
+ if (!parsed?.nodes?.length) return current;
79
+ return {
80
+ ...parsed,
81
+ nodes: parsed.nodes.map((node) => {
82
+ const template = base.nodes.find((n) => n.id === node.id);
83
+ if (!template) return node;
84
+ if (node.id === "api-request") {
85
+ return {
86
+ ...node,
87
+ subtitle: template.subtitle,
88
+ config: { ...template.config, ...node.config, authRef }
89
+ };
90
+ }
91
+ if (node.id === "transform") {
92
+ return { ...node, config: { ...node.config, rootPath } };
93
+ }
94
+ return node;
95
+ })
96
+ };
97
+ });
98
+ }, [registryKey, registryName, authRef, rootPath, registryRow, integrationId, graphUnset, graphBlankShell]);
99
+
100
+ const selectedNode = useMemo(() => {
101
+ const parsed = parseOrchestrationGraph(orchestrationGraph) || orchestrationGraph;
102
+ if (!selectedNodeId || !parsed?.nodes) return null;
103
+ return parsed.nodes.find((n) => String(n.id) === selectedNodeId) || null;
104
+ }, [orchestrationGraph, selectedNodeId]);
105
+
106
+ const graphSerialized = useMemo(
107
+ () => (graphUnset ? "" : serializeOrchestrationGraph(orchestrationGraph)),
108
+ [orchestrationGraph, graphUnset]
109
+ );
110
+
111
+ useEffect(() => {
112
+ if (graphUnset || graphBlankShell) {
113
+ setGraphError(graphBlankShell ? "Add at least Input and API Registry nodes before creating." : "");
114
+ } else {
115
+ const validation = validateOrchestrationGraph(orchestrationGraph);
116
+ setGraphError(validation.ok ? "" : validation.errors[0] || "Invalid graph");
117
+ }
118
+ onDraftChange?.({
119
+ name,
120
+ description,
121
+ runLocality,
122
+ adapter,
123
+ authRef,
124
+ envRefs,
125
+ networkAllow,
126
+ timeoutMs,
127
+ rootPath,
128
+ instructions,
129
+ agentHost,
130
+ schedulerRegistryId,
131
+ orchestrationGraph: graphSerialized
132
+ });
133
+ }, [
134
+ name,
135
+ description,
136
+ runLocality,
137
+ adapter,
138
+ authRef,
139
+ envRefs,
140
+ networkAllow,
141
+ timeoutMs,
142
+ rootPath,
143
+ instructions,
144
+ agentHost,
145
+ schedulerRegistryId,
146
+ graphSerialized,
147
+ orchestrationGraph,
148
+ graphUnset,
149
+ graphBlankShell,
150
+ onDraftChange
151
+ ]);
152
+
153
+ function startFromRegistry() {
154
+ setOrchestrationGraph(buildDefaultOrchestrationGraphFromRegistry(registryRow, { authRef, rootPath }));
155
+ setSelectedNodeId("input");
156
+ setConfigTab("node");
157
+ }
158
+
159
+ function startBlank() {
160
+ setOrchestrationGraph(buildBlankOrchestrationGraphShell());
161
+ setSelectedNodeId("input");
162
+ setConfigTab("node");
163
+ }
164
+
165
+ function applyPastedGraph(text) {
166
+ const parsed = parseOrchestrationGraph(text);
167
+ if (parsed) setOrchestrationGraph(parsed);
168
+ }
169
+
170
+ function addNextNode() {
171
+ if (!nextNodeId) return;
172
+ setOrchestrationGraph((g) => addCanonicalNodeToGraph(
173
+ g || buildBlankOrchestrationGraphShell(),
174
+ nextNodeId,
175
+ registryRow,
176
+ { authRef, rootPath }
177
+ ));
178
+ setSelectedNodeId(nextNodeId);
179
+ setConfigTab("node");
180
+ }
181
+
182
+ function handleNodeConfigChange(configPatch) {
183
+ if (!selectedNodeId) return;
184
+ setOrchestrationGraph((g) => updateGraphNode(g, selectedNodeId, configPatch));
185
+ if (selectedNodeId === "transform" && configPatch.rootPath) {
186
+ setRootPath(String(configPatch.rootPath));
187
+ }
188
+ }
189
+
190
+ function handleConnectorAction(payload) {
191
+ const { nodeId, tab } = resolveConnectorAction(payload);
192
+ setSelectedNodeId(nodeId);
193
+ setConfigTab(tab);
194
+ }
195
+
196
+ const defaultInstructions = `Governed sandbox tool for ${registryName}. Calls ${String(registryRow?.method || "GET").toUpperCase()} ${registryRow?.endpoint || registryRow?.baseUrl || ""}. authRef ${authRef} only — secrets resolve server-side.`;
197
+ const headerBadge = isApiRegistryTestSuccessful(registryRow) ? "connected" : "draft";
198
+ const canCreate = graphUiState === "populated" && !graphError && isApiRegistryTestSuccessful(registryRow);
199
+
200
+ return (
201
+ <section className="dm-orchestration-sidecar" aria-label="Sandbox orchestration field editor">
202
+ <header className="dm-orchestration-header">
203
+ <button type="button" className="dm-orchestration-header__back" onClick={onCancel} aria-label="Back">
204
+ <ArrowLeft size={16} />
205
+ </button>
206
+ <div className="dm-orchestration-header__titles">
207
+ <h2>Sandbox tool draft</h2>
208
+ <p>Created from {registryName}</p>
209
+ </div>
210
+ <span className={`dm-orchestration-header__badge is-${headerBadge}`}>{headerBadge}</span>
211
+ <div className="dm-orchestration-header__actions">
212
+ <button type="button" className="dm-btn-outline" disabled={disabled} onClick={onCancel}>
213
+ Cancel
214
+ </button>
215
+ <button
216
+ type="button"
217
+ className="dm-btn-primary-sm"
218
+ disabled={disabled || !name.trim() || !canCreate}
219
+ onClick={onRequestConfirm}
220
+ >
221
+ Create tool
222
+ </button>
223
+ </div>
224
+ </header>
225
+
226
+ <div className="dm-orchestration-sidecar__body">
227
+ <div className="dm-orchestration-sidecar__canvas-col">
228
+ {graphUnset ? (
229
+ <OrchestrationGraphEmptyCanvas
230
+ disabled={disabled}
231
+ onStartFromRegistry={startFromRegistry}
232
+ onStartBlank={startBlank}
233
+ onPasteGraph={applyPastedGraph}
234
+ />
235
+ ) : graphBlankShell ? (
236
+ <div className="dm-orchestration-canvas dm-orchestration-canvas--blank-shell">
237
+ <p className="dm-orchestration-canvas__blank-hint">Add first node</p>
238
+ <button type="button" className="dm-btn-outline" disabled={disabled} onClick={addNextNode}>
239
+ + Add Input
240
+ </button>
241
+ </div>
242
+ ) : (
243
+ <>
244
+ <OrchestrationGraphCanvas
245
+ graph={orchestrationGraph}
246
+ selectedNodeId={selectedNodeId}
247
+ onSelectNode={(node) => {
248
+ setSelectedNodeId(String(node?.id || ""));
249
+ setConfigTab("node");
250
+ }}
251
+ onConnectorAction={handleConnectorAction}
252
+ />
253
+ {nextNodeId && (
254
+ <button
255
+ type="button"
256
+ className="dm-btn-outline dm-orchestration-canvas__add-node"
257
+ disabled={disabled}
258
+ onClick={addNextNode}
259
+ >
260
+ + Add {nextNodeId === "api-request" ? "API Registry" : nextNodeId === "transform" ? "Transform" : nextNodeId === "result" ? "Result" : "Input"}
261
+ </button>
262
+ )}
263
+ </>
264
+ )}
265
+ </div>
266
+
267
+ <div className="dm-orchestration-sidecar__config-col">
268
+ {graphUiState === "populated" && (
269
+ <OrchestrationNodeConfigPanel
270
+ node={selectedNode}
271
+ registryRow={registryRow}
272
+ disabled={disabled}
273
+ activeTab={configTab}
274
+ onTabChange={setConfigTab}
275
+ onConfigChange={handleNodeConfigChange}
276
+ />
277
+ )}
278
+
279
+ <details className="dm-orchestration-runtime">
280
+ <summary>Runtime (sandbox row)</summary>
281
+ <div className="dm-orchestration-runtime__fields">
282
+ <label className="dm-orchestration-config__field">
283
+ <span>Name</span>
284
+ <input value={name} disabled={disabled} onChange={(e) => setName(e.target.value)} />
285
+ </label>
286
+ <label className="dm-orchestration-config__field">
287
+ <span>Description</span>
288
+ <textarea rows={2} value={description} disabled={disabled} onChange={(e) => setDescription(e.target.value)} />
289
+ </label>
290
+ <label className="dm-orchestration-config__field">
291
+ <span>Run locality</span>
292
+ <select
293
+ value={runLocality}
294
+ disabled={disabled}
295
+ onChange={(e) => {
296
+ const next = e.target.value;
297
+ setRunLocality(next);
298
+ setAdapter(next === "serverless" ? "serverless" : "local-process");
299
+ if (next === "serverless" && !schedulerRegistryId) {
300
+ setSchedulerRegistryId(integrationId);
301
+ }
302
+ }}
303
+ >
304
+ <option value="local">local</option>
305
+ <option value="serverless">serverless</option>
306
+ </select>
307
+ </label>
308
+ <label className="dm-orchestration-config__field">
309
+ <span>Adapter</span>
310
+ <select value={adapter} disabled={disabled} onChange={(e) => setAdapter(e.target.value)}>
311
+ <option value="local-process">local-process</option>
312
+ <option value="local-agent-host">local-agent-host</option>
313
+ <option value="serverless">serverless</option>
314
+ </select>
315
+ </label>
316
+ {adapter === "local-agent-host" && (
317
+ <label className="dm-orchestration-config__field">
318
+ <span>Agent host</span>
319
+ <input value={agentHost} disabled={disabled} onChange={(e) => setAgentHost(e.target.value)} />
320
+ </label>
321
+ )}
322
+ {runLocality === "serverless" && (
323
+ <label className="dm-orchestration-config__field">
324
+ <span>Scheduler registry ID</span>
325
+ <input
326
+ value={schedulerRegistryId}
327
+ disabled={disabled}
328
+ onChange={(e) => setSchedulerRegistryId(e.target.value)}
329
+ />
330
+ </label>
331
+ )}
332
+ <label className="dm-orchestration-config__field">
333
+ <span>Auth reference</span>
334
+ <input value={authRef} disabled={disabled} onChange={(e) => setAuthRef(e.target.value)} />
335
+ </label>
336
+ <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
337
+ <input
338
+ type="checkbox"
339
+ checked={networkAllow}
340
+ disabled={disabled}
341
+ onChange={(e) => setNetworkAllow(e.target.checked)}
342
+ />
343
+ <span>Network allowed</span>
344
+ </label>
345
+ <label className="dm-orchestration-config__field">
346
+ <span>Env refs (comma-separated)</span>
347
+ <input value={envRefs} disabled={disabled} onChange={(e) => setEnvRefs(e.target.value)} />
348
+ </label>
349
+ <label className="dm-orchestration-config__field">
350
+ <span>Timeout (ms)</span>
351
+ <input value={timeoutMs} disabled={disabled} onChange={(e) => setTimeoutMs(e.target.value)} />
352
+ </label>
353
+ <label className="dm-orchestration-config__field">
354
+ <span>Instructions</span>
355
+ <textarea
356
+ rows={3}
357
+ value={instructions || defaultInstructions}
358
+ disabled={disabled}
359
+ onChange={(e) => setInstructions(e.target.value)}
360
+ />
361
+ </label>
362
+ </div>
363
+ </details>
364
+
365
+ {graphError && <p className="dm-orchestration-config__error">{graphError}</p>}
366
+ {graphUnset && (
367
+ <p className="dm-orchestration-config__hint">Start a graph before creating the sandbox tool.</p>
368
+ )}
369
+ <p className="dm-orchestration-sidecar__footnote">
370
+ No secrets are stored. Nothing runs until you click Run sandbox after creation.
371
+ </p>
372
+ </div>
373
+ </div>
374
+ </section>
375
+ );
376
+ }
@@ -1,7 +1,12 @@
1
1
  "use client";
2
2
 
3
+ import { Suspense } from "react";
3
4
  import DataModelShell from "./components/DataModelShell.jsx";
4
5
 
5
6
  export default function DataModelPage() {
6
- return <DataModelShell />;
7
+ return (
8
+ <Suspense fallback={null}>
9
+ <DataModelShell />
10
+ </Suspense>
11
+ );
7
12
  }