@growthub/cli 0.13.2 → 0.13.5

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 (42) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +184 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +24 -2
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +14 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +74 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +67 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +77 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +72 -4
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +123 -27
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +224 -1
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +754 -92
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +224 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +32 -1
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +530 -9
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +8 -1
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +10 -7
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/RunSetupPanel.jsx +261 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +119 -9
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +779 -138
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +91 -14
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +35 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +28 -3
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +216 -5
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +412 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +366 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +34 -3
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-eligibility.js +50 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-redaction.js +64 -0
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +665 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-host-catalog.js +168 -0
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +595 -0
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +164 -7
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +11 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +111 -1
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +14 -0
  42. package/package.json +1 -1
@@ -265,10 +265,152 @@ function normalizeFieldSettings(fieldSettings, columns) {
265
265
  };
266
266
  }
267
267
 
268
- function deriveManualObjectTable(object) {
268
+ /**
269
+ * Resolve a sidecar source-records entry for a live-backed Data Model object.
270
+ *
271
+ * The sidecar (`growthub.source-records.json`) is keyed by `sourceId` — the
272
+ * canonical workspace-source-records key — and the live-backed object can
273
+ * carry that key in three places. We try them in safe fallback order so
274
+ * older configs continue to hydrate without any migration:
275
+ *
276
+ * 1. `object.id` ← refresh-sources writes use object.id as the key
277
+ * 2. `object.sourceId` ← explicit column on the live-backed row
278
+ * 3. `object.binding.sourceId`← canonical binding-level reference
279
+ *
280
+ * Returns `null` when no records are available, so callers fall back to the
281
+ * config-owned `object.rows[]`.
282
+ */
283
+ function findSourceRecordsForObject(object, sourceRecords) {
284
+ if (!sourceRecords || typeof sourceRecords !== "object" || Array.isArray(sourceRecords)) return null;
285
+ const candidates = [object?.id, object?.sourceId, object?.binding?.sourceId]
286
+ .map((value) => (typeof value === "string" ? value.trim() : ""))
287
+ .filter(Boolean);
288
+ for (const key of candidates) {
289
+ const entry = sourceRecords[key];
290
+ if (entry && Array.isArray(entry.records)) return { key, entry };
291
+ }
292
+ return null;
293
+ }
294
+
295
+ /**
296
+ * Infer a lightweight field type for a column from existing rows.
297
+ *
298
+ * This is a runtime projection — it does not persist anywhere and does not
299
+ * require a schema migration. The Chart Hydration Inspector and Twenty-style
300
+ * field-type-aware controls (date granularity, ratio mode, prefix/suffix)
301
+ * read it to decide which UI affordances to show.
302
+ *
303
+ * Returns one of: text | number | boolean | date | select | multi-value |
304
+ * relation-like | json.
305
+ *
306
+ * `existingTypeHints` is the per-table `fieldSettings.types` map — when
307
+ * present it wins over inference, so operators who have already declared a
308
+ * type on the Data Model object don't get overridden by the runtime guesser.
309
+ */
310
+ function inferFieldType(column, rows, existingTypeHints) {
311
+ const hint = existingTypeHints && typeof existingTypeHints === "object" ? existingTypeHints[column] : "";
312
+ if (typeof hint === "string" && hint.trim()) return hint.trim();
313
+ if (!Array.isArray(rows) || rows.length === 0) return "text";
314
+
315
+ const lower = String(column || "").toLowerCase();
316
+ if (/(^|_)(id|_id)$/.test(lower) || lower.endsWith("ref") || lower === "registryid") return "relation-like";
317
+ if (lower.includes("date") || lower.includes("_at") || lower === "created" || lower === "updated" || lower.endsWith("at")) return "date";
318
+
319
+ let numeric = 0;
320
+ let boolean = 0;
321
+ let date = 0;
322
+ let multi = 0;
323
+ let json = 0;
324
+ let nonEmpty = 0;
325
+ const distinct = new Set();
326
+ for (const row of rows) {
327
+ const value = row?.[column];
328
+ if (value === undefined || value === null || value === "") continue;
329
+ nonEmpty += 1;
330
+ if (typeof value === "boolean") boolean += 1;
331
+ if (typeof value === "number" && Number.isFinite(value)) numeric += 1;
332
+ if (typeof value === "string") {
333
+ const trimmed = value.trim();
334
+ const asNumber = Number(trimmed.replace(/,/g, ""));
335
+ if (Number.isFinite(asNumber) && /^-?\d+(\.\d+)?$/.test(trimmed.replace(/,/g, ""))) numeric += 1;
336
+ if (trimmed === "true" || trimmed === "false") boolean += 1;
337
+ if (!Number.isNaN(Date.parse(trimmed)) && /\d{4}/.test(trimmed)) date += 1;
338
+ if (trimmed.includes(",") && trimmed.split(",").length > 1) multi += 1;
339
+ if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) json += 1;
340
+ distinct.add(trimmed);
341
+ } else if (Array.isArray(value)) {
342
+ multi += 1;
343
+ } else if (typeof value === "object") {
344
+ json += 1;
345
+ }
346
+ }
347
+ if (!nonEmpty) return "text";
348
+ if (json / nonEmpty > 0.5) return "json";
349
+ if (date / nonEmpty > 0.6) return "date";
350
+ if (boolean / nonEmpty > 0.6) return "boolean";
351
+ if (numeric / nonEmpty > 0.6) return "number";
352
+ if (multi / nonEmpty > 0.5) return "multi-value";
353
+ // Low-cardinality string columns look like select fields.
354
+ if (nonEmpty >= 3 && distinct.size > 0 && distinct.size <= Math.max(2, Math.floor(nonEmpty / 2))) return "select";
355
+ return "text";
356
+ }
357
+
358
+ function buildFieldMetadata(columns, rows, existingTypeHints) {
359
+ return (Array.isArray(columns) ? columns : []).map((column) => {
360
+ const type = inferFieldType(column, rows, existingTypeHints);
361
+ return {
362
+ id: column,
363
+ label: column,
364
+ type,
365
+ isNumeric: type === "number",
366
+ isDate: type === "date",
367
+ isBoolean: type === "boolean",
368
+ isSelectLike: type === "select",
369
+ isMultiValue: type === "multi-value",
370
+ isRelationLike: type === "relation-like",
371
+ isJson: type === "json"
372
+ };
373
+ });
374
+ }
375
+
376
+ function deriveManualObjectTable(object, options = {}) {
269
377
  const columns = Array.isArray(object.columns) ? object.columns.filter(Boolean) : [];
270
- const rows = Array.isArray(object.rows) ? object.rows.filter((row) => row && typeof row === "object" && !Array.isArray(row)) : [];
378
+ const configRows = Array.isArray(object.rows)
379
+ ? object.rows.filter((row) => row && typeof row === "object" && !Array.isArray(row))
380
+ : [];
271
381
  const source = object.source || object.label || object.name || "Manual object";
382
+
383
+ let hydratedRows = configRows;
384
+ let hydratedColumns = columns;
385
+ let liveSource = null;
386
+ const isLiveBacked = object?.binding?.sourceStorage === "workspace-source-records";
387
+ if (isLiveBacked) {
388
+ const sourceRecords = options?.sourceRecords;
389
+ const match = findSourceRecordsForObject(object, sourceRecords);
390
+ if (match) {
391
+ const records = match.entry.records.filter((row) => row && typeof row === "object" && !Array.isArray(row));
392
+ hydratedRows = records;
393
+ if (!columns.length && records.length) {
394
+ const inferred = new Set();
395
+ records.forEach((row) => Object.keys(row).forEach((key) => inferred.add(key)));
396
+ hydratedColumns = Array.from(inferred);
397
+ }
398
+ liveSource = {
399
+ sourceRecordKey: match.key,
400
+ fetchedAt: typeof match.entry.fetchedAt === "string" ? match.entry.fetchedAt : null,
401
+ recordCount: Number.isFinite(match.entry.recordCount) ? match.entry.recordCount : records.length,
402
+ integrationId: typeof match.entry.integrationId === "string" ? match.entry.integrationId : null
403
+ };
404
+ }
405
+ }
406
+
407
+ const fieldSettings = normalizeFieldSettings(object.fieldSettings, hydratedColumns);
408
+ const sourceBadge = (() => {
409
+ if (isLiveBacked) return "live";
410
+ if (object?.objectType === "data-source") return "api";
411
+ if (object?.objectType === "api-registry") return "api";
412
+ return "manual";
413
+ })();
272
414
  return {
273
415
  id: `manual-object:${object.id || source}`,
274
416
  label: object.label || object.name || source,
@@ -276,15 +418,18 @@ function deriveManualObjectTable(object) {
276
418
  objectType: object.objectType || "custom",
277
419
  icon: object.icon || null,
278
420
  pickerHidden: Boolean(object.pickerHidden),
279
- columns,
280
- rows,
421
+ columns: hydratedColumns,
422
+ rows: hydratedRows,
281
423
  binding: object.binding || { mode: "manual", source: "Data Model" },
282
424
  relations: Array.isArray(object.relations) ? object.relations : [],
283
425
  mutable: true,
284
426
  storage: "manual-object",
285
427
  objectId: object.id,
286
428
  widgetRefs: [],
287
- fieldSettings: normalizeFieldSettings(object.fieldSettings, columns)
429
+ fieldSettings,
430
+ liveSource,
431
+ sourceBadge,
432
+ fieldMetadata: buildFieldMetadata(hydratedColumns, hydratedRows, fieldSettings?.types)
288
433
  };
289
434
  }
290
435
 
@@ -304,7 +449,19 @@ const HIDDEN_HELPER_OBJECT_IDS = new Set([
304
449
  "nav-folders",
305
450
  ]);
306
451
 
307
- function listWorkspaceDataModelTables(workspaceConfig) {
452
+ /**
453
+ * List the workspace Data Model tables — the union of manual `dataModel.objects[]`
454
+ * and the widget-derived tables surfaced for the Data Model browser.
455
+ *
456
+ * @param {object} workspaceConfig — the persisted `growthub.config.json` shape.
457
+ * @param {object} [options]
458
+ * @param {object} [options.sourceRecords] — the sidecar map read from
459
+ * `growthub.source-records.json`. Live-backed objects (binding.sourceStorage
460
+ * === "workspace-source-records") use this to hydrate runtime rows without
461
+ * mutating the persisted config. Pass `undefined` to disable hydration; the
462
+ * config-owned `object.rows[]` is the fallback in either case.
463
+ */
464
+ function listWorkspaceDataModelTables(workspaceConfig, options = {}) {
308
465
  const widgetEntries = listWidgetEntries(workspaceConfig);
309
466
  const refsByObjectId = widgetEntries.reduce((map, { widget, location }) => {
310
467
  const binding = widget?.config?.binding;
@@ -321,7 +478,7 @@ function listWorkspaceDataModelTables(workspaceConfig) {
321
478
  const manualObjects = normalizeManualObjects(workspaceConfig)
322
479
  .filter((object) => !HIDDEN_HELPER_OBJECT_IDS.has(object?.id))
323
480
  .map((object) => {
324
- const table = deriveManualObjectTable(object);
481
+ const table = deriveManualObjectTable(object, options);
325
482
  return { ...table, widgetRefs: refsByObjectId.get(object.id) || [] };
326
483
  });
327
484
  const widgetTables = widgetEntries
@@ -185,6 +185,17 @@ function buildStableSystemPrompt(intent) {
185
185
  `Known object types: ${KNOWN_OBJECT_TYPES.join(", ")}`,
186
186
  `PATCH allowlist (only these top-level keys can be mutated): ${PATCH_ALLOWLIST.join(", ")}`,
187
187
  "",
188
+ "## When configuring dashboard widgets",
189
+ "- Use Data Model objects as source authority.",
190
+ "- Bind widgets by objectId and source metadata.",
191
+ "- For charts, compute widget.config.values from rows.",
192
+ "- Never copy source rows into chart widget config.",
193
+ "- Never store secrets in widget config, Data Model rows, source records, browser state, localStorage, or exported templates.",
194
+ "- Use source records for live-backed data.",
195
+ "- Use filters, grouping, aggregation, style, and values projection as nested widget config only.",
196
+ "- Reset invalid axis, filter, group, and sort settings when source changes.",
197
+ "- Mark recomputed values as unsaved unless PATCH succeeds.",
198
+ "",
188
199
  "## Valid proposal types and their target patch field",
189
200
  WORKSPACE_HELPER_PROPOSAL_TYPES.map(
190
201
  (t) => ` ${t} → ${PROPOSAL_TYPE_TO_PATCH_FIELD[t]}`