@growthub/cli 0.13.1 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +24 -2
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +14 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +74 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +67 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +77 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +48 -3
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +123 -27
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +136 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +713 -92
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +224 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +32 -1
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +514 -9
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +8 -1
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +10 -7
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/RunSetupPanel.jsx +261 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +72 -7
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +778 -140
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +91 -14
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +35 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +15 -3
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +384 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +323 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +32 -3
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-eligibility.js +50 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-redaction.js +64 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +629 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-host-catalog.js +168 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +542 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +164 -7
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +11 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +111 -1
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +9 -0
  33. package/package.json +1 -1
@@ -0,0 +1,261 @@
1
+ "use client";
2
+
3
+ import { useMemo, useState } from "react";
4
+ import { Play, X } from "lucide-react";
5
+ import { RUN_INPUTS_KIND } from "@/lib/orchestration-run-inputs";
6
+
7
+ /**
8
+ * RunSetupPanel — sidecar surface for collecting manual run inputs before a
9
+ * workflow execution. Mounted by WorkflowSurface only when the active
10
+ * orchestration graph declares a `human-input` / form node with required
11
+ * fields. For workflows with no manual inputs, this panel never renders.
12
+ *
13
+ * Invariants:
14
+ * - UI-only validation. The server validates again before persisting.
15
+ * - No secret values are accepted. Fields typed `secretRef` collect only
16
+ * the ref slug. Secret-looking keys (api_key, token, password, etc.)
17
+ * are coerced to secretRef on the server.
18
+ * - The panel reuses .dm-workflow-panel-head, .dm-orchestration-config*,
19
+ * .dm-btn-outline, .dm-workflow-chip-btn. No new design system.
20
+ */
21
+ export function RunSetupPanel({ schema, running, onSubmit, onCancel }) {
22
+ const fields = Array.isArray(schema?.fields) ? schema.fields : [];
23
+ const [values, setValues] = useState(() => seedValues(fields));
24
+ const [touched, setTouched] = useState({});
25
+
26
+ const missing = useMemo(
27
+ () => fields.filter((f) => f.required && !hasValue(values[f.id], f)).map((f) => f.id),
28
+ [fields, values]
29
+ );
30
+
31
+ function patch(fieldId, raw) {
32
+ setValues((current) => ({ ...current, [fieldId]: raw }));
33
+ }
34
+
35
+ function markTouched(fieldId) {
36
+ setTouched((current) => ({ ...current, [fieldId]: true }));
37
+ }
38
+
39
+ function submit() {
40
+ if (missing.length > 0) {
41
+ const next = {};
42
+ for (const id of missing) next[id] = true;
43
+ setTouched((current) => ({ ...current, ...next }));
44
+ return;
45
+ }
46
+ const envelope = {
47
+ kind: RUN_INPUTS_KIND,
48
+ source: "manual",
49
+ values: Object.fromEntries(
50
+ fields
51
+ .map((field) => [field.id, normalizeForSubmission(field, values[field.id])])
52
+ .filter(([, value]) => value !== undefined)
53
+ ),
54
+ files: []
55
+ };
56
+ onSubmit?.(envelope);
57
+ }
58
+
59
+ if (!schema?.requiresInput || fields.length === 0) {
60
+ return (
61
+ <div className="dm-run-setup">
62
+ <p className="dm-run-setup__hint">This workflow has no manual inputs configured.</p>
63
+ <div className="dm-run-setup__actions">
64
+ <button type="button" className="dm-btn-outline" onClick={onCancel}>Close</button>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ return (
71
+ <div className="dm-run-setup">
72
+ {schema.instructions ? (
73
+ <p className="dm-run-setup__instructions">{schema.instructions}</p>
74
+ ) : null}
75
+
76
+ <div className="dm-run-setup__fields">
77
+ {fields.map((field) => (
78
+ <RunSetupField
79
+ key={field.id}
80
+ field={field}
81
+ value={values[field.id]}
82
+ touched={Boolean(touched[field.id])}
83
+ onChange={(raw) => patch(field.id, raw)}
84
+ onBlur={() => markTouched(field.id)}
85
+ />
86
+ ))}
87
+ </div>
88
+
89
+ <div className="dm-run-setup__notice">
90
+ Inputs are validated server-side before the run. Secret-typed fields collect a ref slug only — never a raw value.
91
+ </div>
92
+
93
+ <div className="dm-run-setup__actions">
94
+ <button type="button" className="dm-btn-outline" onClick={onCancel} disabled={running}>
95
+ <X size={13} aria-hidden="true" /> Cancel
96
+ </button>
97
+ <button
98
+ type="button"
99
+ className="dm-workflow-chip-btn"
100
+ onClick={submit}
101
+ disabled={running || missing.length > 0}
102
+ title={missing.length > 0 ? `Fill required fields: ${missing.join(", ")}` : "Run workflow with these inputs"}
103
+ >
104
+ <Play size={13} aria-hidden="true" /> {running ? "Running" : "Run workflow"}
105
+ </button>
106
+ </div>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ function RunSetupField({ field, value, touched, onChange, onBlur }) {
112
+ const showError = touched && field.required && !hasValue(value, field);
113
+ const inputId = `dm-run-setup-${field.id}`;
114
+ const help = field.helpText || (field.type === "secretRef" ? "Reference slug only — server resolves the secret." : "");
115
+
116
+ return (
117
+ <label className="dm-orchestration-config__field" htmlFor={inputId}>
118
+ <span>
119
+ {field.label}
120
+ {field.required ? <em aria-hidden="true" className="dm-run-setup__required"> *</em> : null}
121
+ </span>
122
+ {renderInput(field, value, onChange, onBlur, inputId)}
123
+ {help ? <small className="dm-run-setup__help">{help}</small> : null}
124
+ {showError ? <small className="dm-run-setup__error">Required.</small> : null}
125
+ </label>
126
+ );
127
+ }
128
+
129
+ function renderInput(field, value, onChange, onBlur, inputId) {
130
+ const safeValue = value == null ? "" : value;
131
+ switch (field.type) {
132
+ case "textarea":
133
+ case "json":
134
+ return (
135
+ <textarea
136
+ id={inputId}
137
+ rows={field.type === "json" ? 6 : 4}
138
+ value={typeof safeValue === "string" ? safeValue : JSON.stringify(safeValue, null, 2)}
139
+ onChange={(e) => onChange(e.target.value)}
140
+ onBlur={onBlur}
141
+ placeholder={field.type === "json" ? "{ }" : ""}
142
+ />
143
+ );
144
+ case "boolean":
145
+ case "checkbox":
146
+ return (
147
+ <span className="dm-run-setup__checkbox">
148
+ <input
149
+ id={inputId}
150
+ type="checkbox"
151
+ checked={Boolean(safeValue)}
152
+ onChange={(e) => onChange(e.target.checked)}
153
+ onBlur={onBlur}
154
+ />
155
+ <span>{field.helpText || "Enable"}</span>
156
+ </span>
157
+ );
158
+ case "number":
159
+ case "integer":
160
+ return (
161
+ <input
162
+ id={inputId}
163
+ type="number"
164
+ value={safeValue === "" ? "" : safeValue}
165
+ onChange={(e) => onChange(e.target.value)}
166
+ onBlur={onBlur}
167
+ />
168
+ );
169
+ case "email":
170
+ return (
171
+ <input
172
+ id={inputId}
173
+ type="email"
174
+ value={safeValue}
175
+ onChange={(e) => onChange(e.target.value)}
176
+ onBlur={onBlur}
177
+ placeholder="name@example.com"
178
+ />
179
+ );
180
+ case "url":
181
+ return (
182
+ <input
183
+ id={inputId}
184
+ type="url"
185
+ value={safeValue}
186
+ onChange={(e) => onChange(e.target.value)}
187
+ onBlur={onBlur}
188
+ placeholder="https://"
189
+ />
190
+ );
191
+ case "secretRef":
192
+ return (
193
+ <input
194
+ id={inputId}
195
+ type="text"
196
+ value={safeValue}
197
+ onChange={(e) => onChange(e.target.value)}
198
+ onBlur={onBlur}
199
+ placeholder="ENV_REF_SLUG"
200
+ autoComplete="off"
201
+ />
202
+ );
203
+ default:
204
+ return (
205
+ <input
206
+ id={inputId}
207
+ type="text"
208
+ value={safeValue}
209
+ onChange={(e) => onChange(e.target.value)}
210
+ onBlur={onBlur}
211
+ />
212
+ );
213
+ }
214
+ }
215
+
216
+ function seedValues(fields) {
217
+ const out = {};
218
+ for (const field of fields) {
219
+ if (field.type === "boolean" || field.type === "checkbox") out[field.id] = false;
220
+ else out[field.id] = "";
221
+ }
222
+ return out;
223
+ }
224
+
225
+ function hasValue(value, field) {
226
+ if (field?.type === "boolean" || field?.type === "checkbox") return value === true;
227
+ if (value == null) return false;
228
+ if (typeof value === "string") return value.trim().length > 0;
229
+ if (typeof value === "number") return Number.isFinite(value);
230
+ return Boolean(value);
231
+ }
232
+
233
+ function normalizeForSubmission(field, value) {
234
+ if (field.type === "boolean" || field.type === "checkbox") return Boolean(value);
235
+ if (field.type === "secretRef") {
236
+ const text = typeof value === "string" ? value.trim() : "";
237
+ return text ? { secretRef: text } : undefined;
238
+ }
239
+ if (field.type === "number" || field.type === "integer") {
240
+ if (value === "" || value == null) return undefined;
241
+ const num = Number(value);
242
+ return Number.isFinite(num) ? num : undefined;
243
+ }
244
+ if (field.type === "json") {
245
+ if (typeof value === "string") {
246
+ const trimmed = value.trim();
247
+ if (!trimmed) return undefined;
248
+ try {
249
+ return JSON.parse(trimmed);
250
+ } catch {
251
+ return trimmed;
252
+ }
253
+ }
254
+ return value;
255
+ }
256
+ if (typeof value === "string") {
257
+ const text = value.trim();
258
+ return text ? text : undefined;
259
+ }
260
+ return value;
261
+ }
@@ -45,6 +45,8 @@ import { OrchestrationGraphCanvas } from "../data-model/components/Orchestration
45
45
  import { OrchestrationGraphEmptyCanvas } from "../data-model/components/OrchestrationGraphEmptyCanvas.jsx";
46
46
  import { OrchestrationNodeConfigPanel } from "../data-model/components/OrchestrationNodeConfigPanel.jsx";
47
47
  import { OrchestrationRunTracePanel } from "../data-model/components/OrchestrationRunTracePanel.jsx";
48
+ import { RunSetupPanel } from "./RunSetupPanel.jsx";
49
+ import { discoverRunInputSchema } from "@/lib/orchestration-run-inputs";
48
50
 
49
51
  function resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) {
50
52
  const graph = parseOrchestrationGraph(sandboxRow?.orchestrationGraph);
@@ -328,6 +330,7 @@ export default function WorkflowSurface() {
328
330
  const [graphError, setGraphError] = useState("");
329
331
  const [orchestrationGraph, setOrchestrationGraph] = useState(null);
330
332
  const [dirty, setDirty] = useState(false);
333
+ const [runSetupOpen, setRunSetupOpen] = useState(false);
331
334
 
332
335
  const load = useCallback(async () => {
333
336
  setLoading(true);
@@ -446,6 +449,21 @@ export default function WorkflowSurface() {
446
449
  }
447
450
  }
448
451
 
452
+ async function patchSandboxRuntimeFields(fields) {
453
+ if (resolved.rowIndex < 0 || !objectId || !fields || typeof fields !== "object") return;
454
+ setSaving(true);
455
+ setSaveMessage("");
456
+ try {
457
+ const next = patchSandboxRowInConfig(workspaceConfig, objectId, resolved.rowIndex, fields);
458
+ await persistWorkspace(next);
459
+ setSaveMessage("Updated workflow runtime settings.");
460
+ } catch (err) {
461
+ setSaveMessage(err.message || "Runtime update failed");
462
+ } finally {
463
+ setSaving(false);
464
+ }
465
+ }
466
+
449
467
  async function publishGraph() {
450
468
  if (resolved.rowIndex < 0 || !objectId) return;
451
469
  const serialized = serializeCurrentGraph();
@@ -536,17 +554,27 @@ export default function WorkflowSurface() {
536
554
  }
537
555
  }
538
556
 
539
- async function runSandbox() {
557
+ const runInputSchema = useMemo(
558
+ () => discoverRunInputSchema(orchestrationGraph),
559
+ [orchestrationGraph]
560
+ );
561
+
562
+ async function runSandbox(options = {}) {
540
563
  if (!objectId || !rowId) return;
564
+ const runInputs = options && typeof options === "object" && options.runInputs && typeof options.runInputs === "object"
565
+ ? options.runInputs
566
+ : null;
541
567
  setRunning(true);
542
568
  setRunMessage("");
543
569
  try {
544
570
  const draft = await saveDraft({ orchestrationDraftStatus: "testing" });
545
571
  const draftGraph = draft?.serialized || serializeCurrentGraph();
572
+ const body = { objectId, name: rowId, useDraft: true, draftGraph };
573
+ if (runInputs) body.runInputs = runInputs;
546
574
  const res = await fetch("/api/workspace/sandbox-run", {
547
575
  method: "POST",
548
576
  headers: { "content-type": "application/json" },
549
- body: JSON.stringify({ objectId, name: rowId, useDraft: true, draftGraph }),
577
+ body: JSON.stringify(body),
550
578
  });
551
579
  const payload = await res.json();
552
580
  const responseText = redactSecretsFromText(JSON.stringify(payload.response ?? payload, null, 2));
@@ -586,6 +614,21 @@ export default function WorkflowSurface() {
586
614
  setSidecarMode("trace");
587
615
  }
588
616
 
617
+ function handleTestClick() {
618
+ if (runInputSchema.requiresInput) {
619
+ setRunSetupOpen(true);
620
+ setSelectedNodeId("");
621
+ setAddTarget(null);
622
+ return;
623
+ }
624
+ runSandbox();
625
+ }
626
+
627
+ async function handleRunWithInputs(runInputs) {
628
+ setRunSetupOpen(false);
629
+ await runSandbox({ runInputs });
630
+ }
631
+
589
632
  function openGraphMode() {
590
633
  const params = new URLSearchParams(searchParams.toString());
591
634
  params.delete("run");
@@ -759,8 +802,8 @@ export default function WorkflowSurface() {
759
802
  </button>
760
803
  )}
761
804
  {canTest && (
762
- <button type="button" className="dm-workflow-chip-btn" disabled={running || saving || publishing} onClick={runSandbox}>
763
- <Play size={13} /> {running ? "Running" : "Test"}
805
+ <button type="button" className="dm-workflow-chip-btn" disabled={running || saving || publishing} onClick={handleTestClick}>
806
+ <Play size={13} /> {running ? "Running" : runInputSchema.requiresInput ? "Test with inputs" : "Test"}
764
807
  </button>
765
808
  )}
766
809
  {showPublish && (
@@ -816,9 +859,11 @@ export default function WorkflowSurface() {
816
859
  selectedRunId={runId}
817
860
  onBack={openGraphMode}
818
861
  onOpenGraph={openGraphMode}
862
+ onReplay={runSandbox}
863
+ running={running}
819
864
  />
820
865
  ) : (
821
- <div className={`dm-orchestration-sidecar dm-workflow-orchestration${selectedNode || addTarget ? " has-panel" : ""}`}>
866
+ <div className={`dm-orchestration-sidecar dm-workflow-orchestration${selectedNode || addTarget || runSetupOpen ? " has-panel" : ""}`}>
822
867
  <div className="dm-orchestration-sidecar__body">
823
868
  <div className="dm-orchestration-sidecar__canvas-col">
824
869
  {graphUnset ? (
@@ -855,7 +900,24 @@ export default function WorkflowSurface() {
855
900
  </>
856
901
  )}
857
902
  </div>
858
- {graphUiState === "populated" && addTarget && (
903
+ {graphUiState === "populated" && runSetupOpen && (
904
+ <div className="dm-orchestration-sidecar__config-col">
905
+ <div className="dm-workflow-panel-head">
906
+ <button type="button" className="dm-workflow-icon-btn" onClick={() => setRunSetupOpen(false)} aria-label="Close run setup panel">
907
+ <X size={14} />
908
+ </button>
909
+ <span>Run setup</span>
910
+ <em>Manual inputs</em>
911
+ </div>
912
+ <RunSetupPanel
913
+ schema={runInputSchema}
914
+ running={running}
915
+ onSubmit={handleRunWithInputs}
916
+ onCancel={() => setRunSetupOpen(false)}
917
+ />
918
+ </div>
919
+ )}
920
+ {graphUiState === "populated" && !runSetupOpen && addTarget && (
859
921
  <div className="dm-orchestration-sidecar__config-col">
860
922
  <div className="dm-workflow-panel-head">
861
923
  <button type="button" className="dm-workflow-icon-btn" onClick={() => setAddTarget(null)} aria-label="Close side panel">
@@ -870,7 +932,7 @@ export default function WorkflowSurface() {
870
932
  />
871
933
  </div>
872
934
  )}
873
- {graphUiState === "populated" && !addTarget && selectedNode && (
935
+ {graphUiState === "populated" && !runSetupOpen && !addTarget && selectedNode && (
874
936
  <div className="dm-orchestration-sidecar__config-col">
875
937
  <div className="dm-workflow-panel-head">
876
938
  <button type="button" className="dm-workflow-icon-btn" onClick={() => setSelectedNodeId("")} aria-label="Close side panel">
@@ -884,6 +946,9 @@ export default function WorkflowSurface() {
884
946
  registryRow={registryRow}
885
947
  workspaceConfig={workspaceConfig}
886
948
  sandboxRow={sandboxRow}
949
+ objectId={objectId}
950
+ rowName={rowId}
951
+ onSandboxRowPatch={patchSandboxRuntimeFields}
887
952
  onDeleteNode={deleteSelectedNode}
888
953
  disabled={false}
889
954
  activeTab={configTab}