@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,163 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { ArrowLeft, GitBranch } from "lucide-react";
5
+ import { parseSandboxRunTrace, normalizeRunRecord } from "@/lib/orchestration-run-trace";
6
+ import { redactSecretsFromText } from "@/lib/orchestration-graph";
7
+
8
+ export function OrchestrationRunTracePanel({
9
+ row,
10
+ objectId,
11
+ fieldName,
12
+ selectedRunId,
13
+ onBack,
14
+ onOpenGraph
15
+ }) {
16
+ const [history, setHistory] = useState([]);
17
+ const [historyMessage, setHistoryMessage] = useState("");
18
+ const [loading, setLoading] = useState(false);
19
+ const [activeRunId, setActiveRunId] = useState(String(selectedRunId || row?.lastRunId || "").trim());
20
+
21
+ const rowTrace = useMemo(() => parseSandboxRunTrace(row?.lastResponse), [row?.lastResponse]);
22
+
23
+ useEffect(() => {
24
+ setActiveRunId(String(selectedRunId || row?.lastRunId || "").trim());
25
+ }, [selectedRunId, row?.lastRunId, row?.lastResponse]);
26
+
27
+ useEffect(() => {
28
+ const objectIdValue = String(objectId || "").trim();
29
+ const name = String(row?.Name || "").trim();
30
+ if (!objectIdValue || !name) return;
31
+ setLoading(true);
32
+ setHistoryMessage("");
33
+ fetch(`/api/workspace/sandbox-run?objectId=${encodeURIComponent(objectIdValue)}&name=${encodeURIComponent(name)}`, {
34
+ cache: "no-store"
35
+ })
36
+ .then((res) => res.json())
37
+ .then((payload) => {
38
+ if (!payload.ok) throw new Error(payload.error || "Could not load run history");
39
+ setHistory(Array.isArray(payload.records) ? payload.records.map(normalizeRunRecord).filter(Boolean) : []);
40
+ setHistoryMessage(`${payload.recordCount || 0} saved run${payload.recordCount === 1 ? "" : "s"}`);
41
+ })
42
+ .catch((err) => {
43
+ setHistory([]);
44
+ setHistoryMessage(err.message || "Could not load run history");
45
+ })
46
+ .finally(() => setLoading(false));
47
+ }, [objectId, row?.Name]);
48
+
49
+ const activeRecord = useMemo(() => {
50
+ if (activeRunId && history.length) {
51
+ const match = history.find((r) => r.runId === activeRunId);
52
+ if (match) return match;
53
+ }
54
+ if (fieldName === "lastResponse" || !activeRunId) {
55
+ return {
56
+ runId: rowTrace.runId || row?.lastRunId || "",
57
+ ranAt: rowTrace.ranAt || row?.lastTested || "",
58
+ exitCode: rowTrace.exitCode,
59
+ durationMs: rowTrace.durationMs,
60
+ error: rowTrace.error,
61
+ stdout: rowTrace.stdout,
62
+ stderr: rowTrace.stderr,
63
+ output: rowTrace.output
64
+ };
65
+ }
66
+ return history[0] || null;
67
+ }, [activeRunId, history, rowTrace, fieldName, row?.lastRunId, row?.lastTested]);
68
+
69
+ const summary = fieldName === "lastSourceId"
70
+ ? `Source ${String(row?.lastSourceId || "").trim()}`
71
+ : fieldName === "lastRunId"
72
+ ? `Run ${String(row?.lastRunId || activeRunId || "").trim()}`
73
+ : "Latest sandbox run";
74
+
75
+ return (
76
+ <section className="dm-orchestration-trace" aria-label="Run trace viewer">
77
+ <header className="dm-orchestration-trace__head">
78
+ <button type="button" className="dm-orchestration-header__back" onClick={onBack} aria-label="Back to record">
79
+ <ArrowLeft size={16} />
80
+ </button>
81
+ <div>
82
+ <h2>Run trace</h2>
83
+ <p>{summary} · {row?.Name || "Sandbox tool"}</p>
84
+ </div>
85
+ {onOpenGraph && (
86
+ <button type="button" className="dm-btn-outline" onClick={onOpenGraph}>
87
+ <GitBranch size={14} aria-hidden="true" />
88
+ Edit graph
89
+ </button>
90
+ )}
91
+ </header>
92
+
93
+ <div className="dm-orchestration-trace__body">
94
+ <aside className="dm-orchestration-trace__list">
95
+ <p className="dm-orchestration-trace__list-title">Run history</p>
96
+ {loading && <p className="dm-orchestration-config__hint">Loading…</p>}
97
+ {!loading && historyMessage && <p className="dm-orchestration-config__hint">{historyMessage}</p>}
98
+ <button
99
+ type="button"
100
+ className={`dm-orchestration-trace__run${!activeRunId ? " is-active" : ""}`}
101
+ onClick={() => setActiveRunId("")}
102
+ >
103
+ <span>Row preview</span>
104
+ <span>{row?.status || rowTrace.status || "—"} · {row?.lastTested || rowTrace.ranAt || "—"}</span>
105
+ </button>
106
+ {history.map((record) => (
107
+ <button
108
+ key={record.runId || record.ranAt}
109
+ type="button"
110
+ className={`dm-orchestration-trace__run${activeRunId === record.runId ? " is-active" : ""}`}
111
+ onClick={() => setActiveRunId(record.runId)}
112
+ >
113
+ <span>{record.runId || "run"}</span>
114
+ <span>
115
+ {record.exitCode === 0 && !record.error ? "success" : "failed"}
116
+ {record.ranAt ? ` · ${record.ranAt}` : ""}
117
+ </span>
118
+ </button>
119
+ ))}
120
+ </aside>
121
+
122
+ <div className="dm-orchestration-trace__detail">
123
+ <dl className="dm-orchestration-trace__meta">
124
+ <div><dt>Status</dt><dd>{row?.status || rowTrace.status || (activeRecord?.exitCode === 0 ? "connected" : "—")}</dd></div>
125
+ <div><dt>Run ID</dt><dd>{activeRecord?.runId || row?.lastRunId || "—"}</dd></div>
126
+ <div><dt>Exit code</dt><dd>{activeRecord?.exitCode ?? rowTrace.exitCode ?? "—"}</dd></div>
127
+ <div><dt>Duration</dt><dd>{activeRecord?.durationMs ?? rowTrace.durationMs ?? "—"} ms</dd></div>
128
+ <div><dt>Runtime</dt><dd>{rowTrace.runtime || row?.runtime || "—"}</dd></div>
129
+ <div><dt>Adapter</dt><dd>{rowTrace.adapter || row?.adapter || "—"}</dd></div>
130
+ <div><dt>Run locality</dt><dd>{rowTrace.runLocality || row?.runLocality || "—"}</dd></div>
131
+ <div><dt>Tested</dt><dd>{activeRecord?.ranAt || row?.lastTested || rowTrace.ranAt || "—"}</dd></div>
132
+ </dl>
133
+
134
+ {(activeRecord?.error || rowTrace.error) && (
135
+ <div className="dm-orchestration-trace__error">
136
+ <span>Error</span>
137
+ <pre>{redactSecretsFromText(activeRecord?.error || rowTrace.error)}</pre>
138
+ </div>
139
+ )}
140
+
141
+ <div className="dm-orchestration-trace__output">
142
+ <span>Stdout</span>
143
+ <pre>{redactSecretsFromText(activeRecord?.stdout || rowTrace.stdout || "—")}</pre>
144
+ </div>
145
+
146
+ {(activeRecord?.output || rowTrace.output) && (
147
+ <div className="dm-orchestration-trace__output">
148
+ <span>Normalized output</span>
149
+ <pre>{redactSecretsFromText(activeRecord?.output || rowTrace.output)}</pre>
150
+ </div>
151
+ )}
152
+
153
+ {(activeRecord?.stderr || rowTrace.stderr) && (
154
+ <div className="dm-orchestration-trace__output">
155
+ <span>Stderr</span>
156
+ <pre>{redactSecretsFromText(activeRecord?.stderr || rowTrace.stderr)}</pre>
157
+ </div>
158
+ )}
159
+ </div>
160
+ </div>
161
+ </section>
162
+ );
163
+ }
@@ -0,0 +1,190 @@
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
+ getOrchestrationGraphUiState,
11
+ parseOrchestrationGraph,
12
+ serializeOrchestrationGraph,
13
+ updateGraphNode,
14
+ validateOrchestrationGraph
15
+ } from "@/lib/orchestration-graph";
16
+ import { resolveConnectorAction } from "@/lib/orchestration-sidecar-routing";
17
+ import { OrchestrationGraphCanvas } from "./OrchestrationGraphCanvas.jsx";
18
+ import { OrchestrationGraphEmptyCanvas } from "./OrchestrationGraphEmptyCanvas.jsx";
19
+ import { OrchestrationNodeConfigPanel } from "./OrchestrationNodeConfigPanel.jsx";
20
+
21
+ function resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) {
22
+ const graph = parseOrchestrationGraph(sandboxRow?.orchestrationGraph ?? sandboxRow?.orchestrationConfig);
23
+ const apiNode = graph?.nodes?.find((n) => n?.type === "api-registry-call");
24
+ const registryId = String(
25
+ apiNode?.config?.registryId || apiNode?.config?.integrationId || sandboxRow?.schedulerRegistryId || ""
26
+ ).trim();
27
+ if (!registryId || !workspaceConfig) return null;
28
+ const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
29
+ for (const objectItem of objects) {
30
+ if (objectItem?.objectType !== "api-registry") continue;
31
+ const rows = Array.isArray(objectItem.rows) ? objectItem.rows : [];
32
+ const match = rows.find((r) => String(r?.integrationId || "").trim() === registryId);
33
+ if (match) return match;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ export function SandboxOrchestrationEditorPanel({
39
+ sandboxRow,
40
+ workspaceConfig,
41
+ disabled,
42
+ onSaveGraph,
43
+ onBack
44
+ }) {
45
+ const registryRow = useMemo(
46
+ () => resolveRegistryRowForSandbox(workspaceConfig, sandboxRow),
47
+ [workspaceConfig, sandboxRow]
48
+ );
49
+
50
+ const [selectedNodeId, setSelectedNodeId] = useState("input");
51
+ const [configTab, setConfigTab] = useState("node");
52
+ const [graphError, setGraphError] = useState("");
53
+ const savedGraph = sandboxRow?.orchestrationGraph ?? sandboxRow?.orchestrationConfig;
54
+ const [orchestrationGraph, setOrchestrationGraph] = useState(() => parseOrchestrationGraph(savedGraph));
55
+
56
+ const graphUiState = getOrchestrationGraphUiState(
57
+ orchestrationGraph ?? savedGraph
58
+ );
59
+ const graphUnset = graphUiState === "unset";
60
+ const graphBlankShell = graphUiState === "blank-shell";
61
+ const nextNodeId = useMemo(
62
+ () => (orchestrationGraph ? getNextCanonicalNodeId(orchestrationGraph) : "input"),
63
+ [orchestrationGraph]
64
+ );
65
+
66
+ const selectedNode = useMemo(() => {
67
+ if (!orchestrationGraph?.nodes || !selectedNodeId) return null;
68
+ return orchestrationGraph.nodes.find((n) => String(n.id) === selectedNodeId) || null;
69
+ }, [orchestrationGraph, selectedNodeId]);
70
+
71
+ useEffect(() => {
72
+ setOrchestrationGraph(parseOrchestrationGraph(sandboxRow?.orchestrationGraph ?? sandboxRow?.orchestrationConfig));
73
+ }, [sandboxRow?.orchestrationGraph, sandboxRow?.orchestrationConfig]);
74
+
75
+ useEffect(() => {
76
+ if (graphUnset) {
77
+ setGraphError("");
78
+ return;
79
+ }
80
+ if (graphBlankShell) {
81
+ setGraphError("");
82
+ onSaveGraph?.(serializeOrchestrationGraph(orchestrationGraph));
83
+ return;
84
+ }
85
+ const validation = validateOrchestrationGraph(orchestrationGraph);
86
+ setGraphError(validation.ok ? "" : validation.errors[0] || "Invalid graph");
87
+ onSaveGraph?.(serializeOrchestrationGraph(orchestrationGraph));
88
+ }, [orchestrationGraph, graphUnset, graphBlankShell, onSaveGraph]);
89
+
90
+ function startFromRegistry() {
91
+ if (!registryRow) return;
92
+ setOrchestrationGraph(buildDefaultOrchestrationGraphFromRegistry(registryRow));
93
+ setSelectedNodeId("input");
94
+ setConfigTab("node");
95
+ }
96
+
97
+ function startBlank() {
98
+ setOrchestrationGraph(buildBlankOrchestrationGraphShell());
99
+ setSelectedNodeId("input");
100
+ setConfigTab("node");
101
+ }
102
+
103
+ function applyPastedGraph(text) {
104
+ const parsed = parseOrchestrationGraph(text);
105
+ if (parsed) setOrchestrationGraph(parsed);
106
+ }
107
+
108
+ function addNextNode() {
109
+ if (!nextNodeId) return;
110
+ setOrchestrationGraph((g) => addCanonicalNodeToGraph(g || buildBlankOrchestrationGraphShell(), nextNodeId, registryRow || {}));
111
+ setSelectedNodeId(nextNodeId);
112
+ setConfigTab("node");
113
+ }
114
+
115
+ function handleConnectorAction(payload) {
116
+ const { nodeId, tab } = resolveConnectorAction(payload);
117
+ setSelectedNodeId(nodeId);
118
+ setConfigTab(tab);
119
+ }
120
+
121
+ function handleNodeConfigChange(configPatch) {
122
+ if (!selectedNodeId) return;
123
+ setOrchestrationGraph((g) => updateGraphNode(g, selectedNodeId, configPatch));
124
+ }
125
+
126
+ return (
127
+ <section className="dm-orchestration-sidecar" aria-label="Sandbox orchestration graph editor">
128
+ <header className="dm-orchestration-header">
129
+ <button type="button" className="dm-orchestration-header__back" onClick={onBack} aria-label="Back">
130
+ <ArrowLeft size={16} />
131
+ </button>
132
+ <div className="dm-orchestration-header__titles">
133
+ <h2>Orchestration graph</h2>
134
+ <p>{sandboxRow?.Name || "Sandbox tool"}</p>
135
+ </div>
136
+ </header>
137
+
138
+ <div className="dm-orchestration-sidecar__body">
139
+ <div className="dm-orchestration-sidecar__canvas-col">
140
+ {graphUnset ? (
141
+ <OrchestrationGraphEmptyCanvas
142
+ disabled={disabled || !registryRow}
143
+ onStartFromRegistry={registryRow ? startFromRegistry : undefined}
144
+ onStartBlank={startBlank}
145
+ onPasteGraph={applyPastedGraph}
146
+ />
147
+ ) : graphBlankShell ? (
148
+ <div className="dm-orchestration-canvas dm-orchestration-canvas--blank-shell">
149
+ <p className="dm-orchestration-canvas__blank-hint">Add first node</p>
150
+ <button type="button" className="dm-btn-outline" disabled={disabled} onClick={addNextNode}>
151
+ + Add Input
152
+ </button>
153
+ </div>
154
+ ) : (
155
+ <>
156
+ <OrchestrationGraphCanvas
157
+ graph={orchestrationGraph}
158
+ selectedNodeId={selectedNodeId}
159
+ onSelectNode={(node) => {
160
+ setSelectedNodeId(String(node?.id || ""));
161
+ setConfigTab("node");
162
+ }}
163
+ onConnectorAction={handleConnectorAction}
164
+ />
165
+ {nextNodeId && (
166
+ <button type="button" className="dm-btn-outline dm-orchestration-canvas__add-node" disabled={disabled} onClick={addNextNode}>
167
+ + Add {nextNodeId === "api-request" ? "API Registry" : nextNodeId}
168
+ </button>
169
+ )}
170
+ </>
171
+ )}
172
+ </div>
173
+
174
+ {graphUiState === "populated" && (
175
+ <div className="dm-orchestration-sidecar__config-col">
176
+ <OrchestrationNodeConfigPanel
177
+ node={selectedNode}
178
+ registryRow={registryRow}
179
+ disabled={disabled}
180
+ activeTab={configTab}
181
+ onTabChange={setConfigTab}
182
+ onConfigChange={handleNodeConfigChange}
183
+ />
184
+ {graphError && <p className="dm-orchestration-config__error">{graphError}</p>}
185
+ </div>
186
+ )}
187
+ </div>
188
+ </section>
189
+ );
190
+ }
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import { X } from "lucide-react";
4
+ import { summarizeOrchestrationGraph } from "@/lib/orchestration-graph";
5
+
6
+ export function SandboxToolConfirmModal({
7
+ open,
8
+ toolName,
9
+ authRef,
10
+ orchestrationGraph,
11
+ onConfirm,
12
+ onCancel,
13
+ creating
14
+ }) {
15
+ if (!open) return null;
16
+
17
+ const summary = summarizeOrchestrationGraph(orchestrationGraph);
18
+
19
+ return (
20
+ <div className="dm-orchestration-confirm dm-orchestration-confirm__backdrop" onClick={onCancel} role="presentation">
21
+ <section
22
+ className="dm-orchestration-confirm__dialog"
23
+ role="dialog"
24
+ aria-modal="true"
25
+ aria-labelledby="sandbox-tool-confirm-title"
26
+ onClick={(event) => event.stopPropagation()}
27
+ >
28
+ <header className="dm-orchestration-confirm__head">
29
+ <div>
30
+ <p>Confirm</p>
31
+ <h2 id="sandbox-tool-confirm-title">Create sandbox tool?</h2>
32
+ </div>
33
+ <button type="button" className="dm-sidebar-close" onClick={onCancel} aria-label="Close">
34
+ <X size={16} />
35
+ </button>
36
+ </header>
37
+ <div className="dm-orchestration-confirm__body">
38
+ <p>This creates one Sandbox Environment row from the tested API Registry record.</p>
39
+ <ul className="dm-orchestration-confirm__list">
40
+ <li>Saves orchestrationGraph on the sandbox row</li>
41
+ <li>Stores <code>{authRef || "authRef"}</code> only — no secrets</li>
42
+ <li>Does not store secrets</li>
43
+ <li>Does not create widgets</li>
44
+ <li>Does not change dashboards</li>
45
+ <li>Does not change canvas</li>
46
+ <li>Does not run until you click Run sandbox</li>
47
+ </ul>
48
+ <p className="dm-orchestration-confirm__summary">
49
+ <span>Run plan</span>
50
+ {summary}
51
+ </p>
52
+ </div>
53
+ <footer className="dm-orchestration-confirm__foot">
54
+ <button type="button" className="dm-btn-outline" disabled={creating} onClick={onCancel}>
55
+ Cancel
56
+ </button>
57
+ <button type="button" className="dm-btn-primary-sm" disabled={creating} onClick={onConfirm}>
58
+ {creating ? "Creating…" : "Create tool"}
59
+ </button>
60
+ </footer>
61
+ </section>
62
+ </div>
63
+ );
64
+ }