@growthub/cli 0.9.18 → 0.10.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 (56) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/reference-options/route.js +62 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +13 -2
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolver-templates/route.js +23 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +35 -5
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +15 -1
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +2277 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataTable.jsx +1 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/FieldEditor.jsx +1 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/FieldManager.jsx +9 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ObjectSidebar.jsx +41 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/RecordDrawer.jsx +1 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +244 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +21 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SourceTestPanel.jsx +15 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/StatusPill.jsx +13 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ToggleField.jsx +41 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/dm-shared.jsx +99 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +2 -1528
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +99 -6
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/connector-template-authoring.md +8 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/data-model-reference-fields.md +15 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/mcp-chrome-tool-connectors.md +12 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/resolver-template-library.md +17 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +13 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/README.md +12 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/chrome-bridge.js +22 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/custom-http.js +23 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-commerce.js +22 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-crm.js +23 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-project-management.js +22 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/generic-spreadsheet.js +22 -0
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/mcp-tool.js +22 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +50 -0
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/webhook.js +22 -0
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/collect-reference-options.js +133 -0
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/reference-resolver-registry.js +17 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolver-loader.js +6 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/README.md +8 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/local-data-model.js +11 -0
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/references/resolvers/source-records.js +34 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/adapters/README.md +5 -3
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +203 -0
  43. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/index.js +1 -0
  44. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +81 -0
  45. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/reference-option-schema.js +59 -0
  46. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/reference-options.js +29 -0
  47. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +534 -23
  48. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +131 -1
  49. package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/export-training-traces.mjs +144 -0
  50. package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/grade-raw-pairs.mjs +279 -0
  51. package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/harvest-cursor-traces.mjs +288 -0
  52. package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/upload-graded-traces.mjs +128 -0
  53. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +10 -0
  54. package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/alignment-loop.config.json +264 -0
  55. package/dist/index.js +486 -1
  56. package/package.json +1 -1
@@ -163,6 +163,108 @@ function normalizeManualObjects(workspaceConfig) {
163
163
  return Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
164
164
  }
165
165
 
166
+ function normalizeStringList(values) {
167
+ return Array.from(new Set((Array.isArray(values) ? values : [])
168
+ .map((value) => String(value || "").trim())
169
+ .filter(Boolean)));
170
+ }
171
+
172
+ function normalizeSortClauses(sort, columns) {
173
+ const allowed = new Set(columns);
174
+ return (Array.isArray(sort) ? sort : []).flatMap((clause) => {
175
+ if (!clause || typeof clause !== "object" || Array.isArray(clause)) return [];
176
+ const fieldId = String(clause.fieldId || "").trim();
177
+ const direction = String(clause.direction || "asc").trim().toLowerCase() === "desc" ? "desc" : "asc";
178
+ return allowed.has(fieldId) ? [{ fieldId, direction }] : [];
179
+ });
180
+ }
181
+
182
+ function normalizeFilterConfig(filter, columns) {
183
+ if (!filter || typeof filter !== "object" || Array.isArray(filter)) return undefined;
184
+ const allowed = new Set(columns);
185
+ const op = String(filter.op || "and").trim().toLowerCase() === "or" ? "or" : "and";
186
+ const clauses = (Array.isArray(filter.clauses) ? filter.clauses : []).flatMap((clause) => {
187
+ if (!clause || typeof clause !== "object" || Array.isArray(clause)) return [];
188
+ const fieldId = String(clause.fieldId || "").trim();
189
+ const operator = String(clause.operator || "eq").trim();
190
+ if (!allowed.has(fieldId)) return [];
191
+ if (["isEmpty", "isNotEmpty"].includes(operator)) return [{ fieldId, operator }];
192
+ const value = clause.value;
193
+ if (value === undefined || value === null || value === "") return [];
194
+ return [{ fieldId, operator, value }];
195
+ });
196
+ return clauses.length ? { op, clauses } : undefined;
197
+ }
198
+
199
+ function normalizeFieldTypes(types, columns) {
200
+ const result = {};
201
+ const source = types && typeof types === "object" && !Array.isArray(types) ? types : {};
202
+ columns.forEach((column) => {
203
+ const value = String(source[column] || "").trim();
204
+ if (value) result[column] = value;
205
+ });
206
+ return result;
207
+ }
208
+
209
+ function snapshotTableViewState(settings) {
210
+ return {
211
+ hidden: normalizeStringList(settings?.hidden),
212
+ order: normalizeStringList(settings?.order),
213
+ sort: Array.isArray(settings?.sort) ? settings.sort.map((clause) => ({ ...clause })) : [],
214
+ filter: settings?.filter ? {
215
+ op: settings.filter.op,
216
+ clauses: (settings.filter.clauses || []).map((clause) => ({ ...clause }))
217
+ } : undefined
218
+ };
219
+ }
220
+
221
+ function normalizeSavedViews(views, columns) {
222
+ return (Array.isArray(views) ? views : []).flatMap((view, index) => {
223
+ if (!view || typeof view !== "object" || Array.isArray(view)) return [];
224
+ const id = String(view.id || "").trim() || `view-${index + 1}`;
225
+ const name = String(view.name || "").trim();
226
+ if (!name) return [];
227
+ const state = normalizeFieldSettings({
228
+ hidden: view.hidden,
229
+ order: view.order,
230
+ sort: view.sort,
231
+ filter: view.filter,
232
+ }, columns);
233
+ return [{
234
+ id,
235
+ name,
236
+ favorite: Boolean(view.favorite),
237
+ locked: Boolean(view.locked),
238
+ hidden: state.hidden,
239
+ order: state.order,
240
+ sort: state.sort,
241
+ filter: state.filter
242
+ }];
243
+ });
244
+ }
245
+
246
+ function normalizeFieldSettings(fieldSettings, columns) {
247
+ const order = normalizeStringList([
248
+ ...(Array.isArray(fieldSettings?.order) ? fieldSettings.order : []),
249
+ ...columns
250
+ ]).filter((column) => columns.includes(column));
251
+ const hidden = normalizeStringList(fieldSettings?.hidden).filter((column) => columns.includes(column));
252
+ const sort = normalizeSortClauses(fieldSettings?.sort, columns);
253
+ const filter = normalizeFilterConfig(fieldSettings?.filter, columns);
254
+ const views = normalizeSavedViews(fieldSettings?.views, columns);
255
+ const activeViewId = String(fieldSettings?.activeViewId || "").trim();
256
+ return {
257
+ hidden,
258
+ order,
259
+ sort,
260
+ filter,
261
+ types: normalizeFieldTypes(fieldSettings?.types, columns),
262
+ views,
263
+ activeViewId: views.some((view) => view.id === activeViewId) ? activeViewId : "",
264
+ favorite: Boolean(fieldSettings?.favorite)
265
+ };
266
+ }
267
+
166
268
  function deriveManualObjectTable(object) {
167
269
  const columns = Array.isArray(object.columns) ? object.columns.filter(Boolean) : [];
168
270
  const rows = Array.isArray(object.rows) ? object.rows.filter((row) => row && typeof row === "object" && !Array.isArray(row)) : [];
@@ -173,6 +275,7 @@ function deriveManualObjectTable(object) {
173
275
  source,
174
276
  objectType: object.objectType || "custom",
175
277
  icon: object.icon || null,
278
+ pickerHidden: Boolean(object.pickerHidden),
176
279
  columns,
177
280
  rows,
178
281
  binding: object.binding || { mode: "manual", source: "Data Model" },
@@ -181,10 +284,7 @@ function deriveManualObjectTable(object) {
181
284
  storage: "manual-object",
182
285
  objectId: object.id,
183
286
  widgetRefs: [],
184
- fieldSettings: {
185
- hidden: Array.isArray(object.fieldSettings?.hidden) ? object.fieldSettings.hidden : [],
186
- order: Array.isArray(object.fieldSettings?.order) ? object.fieldSettings.order : columns
187
- }
287
+ fieldSettings: normalizeFieldSettings(object.fieldSettings, columns)
188
288
  };
189
289
  }
190
290
 
@@ -224,10 +324,11 @@ function listWorkspaceDataModelTables(workspaceConfig) {
224
324
  widgetTitle: widget.title,
225
325
  widgetKind: widget.kind
226
326
  }],
227
- fieldSettings: {
228
- hidden: Array.isArray(widget.config?.fieldSettings?.hidden) ? widget.config.fieldSettings.hidden : [],
229
- order: Array.isArray(widget.config?.fieldSettings?.order) ? widget.config.fieldSettings.order : table.columns
230
- }
327
+ fieldSettings: normalizeFieldSettings({
328
+ ...(widget.config?.fieldSettings || {}),
329
+ sort: widget.config?.sort,
330
+ filter: widget.config?.filter
331
+ }, table.columns)
231
332
  };
232
333
  })
233
334
  .filter(Boolean);
@@ -235,25 +336,194 @@ function listWorkspaceDataModelTables(workspaceConfig) {
235
336
  }
236
337
 
237
338
  function writeTableConfig(config, storage, columns, rows) {
339
+ const fieldSettings = normalizeFieldSettings({
340
+ ...(config.fieldSettings || {}),
341
+ sort: config.sort,
342
+ filter: config.filter
343
+ }, columns);
238
344
  if (storage === "view") {
239
345
  const binding = config.binding?.mode === "manual" ? { ...config.binding, rows } : config.binding;
240
- return { ...config, columns, rows, binding, fieldSettings: { hidden: config.fieldSettings?.hidden || [], order: columns } };
346
+ return { ...config, columns, rows, binding, fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
241
347
  }
242
348
  if (storage === "json") {
243
- return { ...config, binding: { ...config.binding, json: JSON.stringify(rows, null, 2) } };
349
+ return { ...config, binding: { ...config.binding, json: JSON.stringify(rows, null, 2) }, fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
244
350
  }
245
351
  if (storage === "csv") {
246
- return { ...config, binding: { ...config.binding, csv: toCsv(columns, rows) } };
352
+ return { ...config, binding: { ...config.binding, csv: toCsv(columns, rows) }, fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
247
353
  }
248
354
  if (storage === "manual-binding") {
249
- return { ...config, binding: { ...config.binding, rows } };
355
+ return { ...config, binding: { ...config.binding, rows }, fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
250
356
  }
251
357
  if (storage === "chart-values") {
252
- return { ...config, values: rows.map((row) => Number(row.Value)).filter((value) => Number.isFinite(value)) };
358
+ return { ...config, values: rows.map((row) => Number(row.Value)).filter((value) => Number.isFinite(value)), fieldSettings, sort: fieldSettings.sort, filter: fieldSettings.filter };
253
359
  }
254
360
  return config;
255
361
  }
256
362
 
363
+ function updateTableFieldSettings(workspaceConfig, table, updater) {
364
+ const nextSettings = normalizeFieldSettings(
365
+ updater(normalizeFieldSettings(table.fieldSettings, table.columns)),
366
+ table.columns
367
+ );
368
+ if (table.storage === "manual-object") {
369
+ const objects = normalizeManualObjects(workspaceConfig);
370
+ const dataModel = workspaceConfig.dataModel && typeof workspaceConfig.dataModel === "object" && !Array.isArray(workspaceConfig.dataModel)
371
+ ? workspaceConfig.dataModel
372
+ : {};
373
+ return {
374
+ ...workspaceConfig,
375
+ dataModel: {
376
+ ...dataModel,
377
+ objects: objects.map((object) => object.id === table.objectId
378
+ ? { ...object, fieldSettings: nextSettings }
379
+ : object)
380
+ }
381
+ };
382
+ }
383
+
384
+ const ids = new Set((table.widgetRefs || []).map((ref) => ref.widgetId));
385
+ const mutateWidgets = (widgets) => (widgets || []).map((widget) => {
386
+ if (!ids.has(widget.id)) return widget;
387
+ const current = deriveWidgetTable(widget, { widgetId: widget.id });
388
+ if (!current?.mutable) return widget;
389
+ return {
390
+ ...widget,
391
+ config: {
392
+ ...(widget.config || {}),
393
+ fieldSettings: nextSettings,
394
+ sort: nextSettings.sort,
395
+ filter: nextSettings.filter
396
+ }
397
+ };
398
+ });
399
+
400
+ const dashboards = (workspaceConfig.dashboards || []).map((dashboard) => ({
401
+ ...dashboard,
402
+ tabs: (dashboard.tabs || []).map((tab) => ({ ...tab, widgets: mutateWidgets(tab.widgets) }))
403
+ }));
404
+ let canvas = workspaceConfig.canvas ? { ...workspaceConfig.canvas } : {};
405
+ if (Array.isArray(canvas.widgets)) canvas = { ...canvas, widgets: mutateWidgets(canvas.widgets) };
406
+ if (Array.isArray(canvas.tabs)) canvas = { ...canvas, tabs: canvas.tabs.map((tab) => ({ ...tab, widgets: mutateWidgets(tab.widgets) })) };
407
+ return { ...workspaceConfig, dashboards, canvas };
408
+ }
409
+
410
+ function remapFieldName(name, renameMap = {}) {
411
+ return renameMap[name] || name;
412
+ }
413
+
414
+ function remapFieldSettings(fieldSettings, columns, renameMap = {}) {
415
+ const mapList = (values) => normalizeStringList((values || []).map((value) => remapFieldName(value, renameMap))).filter((value) => columns.includes(value));
416
+ const mapSort = (sort) => normalizeSortClauses((sort || []).map((clause) => ({
417
+ ...clause,
418
+ fieldId: remapFieldName(clause.fieldId, renameMap)
419
+ })), columns);
420
+ const mapFilter = (filter) => normalizeFilterConfig(filter ? {
421
+ ...filter,
422
+ clauses: (filter.clauses || []).map((clause) => ({
423
+ ...clause,
424
+ fieldId: remapFieldName(clause.fieldId, renameMap)
425
+ }))
426
+ } : null, columns);
427
+ const sourceTypes = fieldSettings?.types && typeof fieldSettings.types === "object" && !Array.isArray(fieldSettings.types)
428
+ ? fieldSettings.types
429
+ : {};
430
+ const types = columns.reduce((acc, column) => {
431
+ const previousKey = Object.keys(renameMap).find((key) => renameMap[key] === column) || column;
432
+ const typeValue = sourceTypes[column] || sourceTypes[previousKey];
433
+ if (typeValue) acc[column] = typeValue;
434
+ return acc;
435
+ }, {});
436
+ const views = normalizeSavedViews((fieldSettings?.views || []).map((view) => ({
437
+ ...view,
438
+ hidden: mapList(view.hidden || []),
439
+ order: mapList(view.order || []),
440
+ sort: mapSort(view.sort || []),
441
+ filter: mapFilter(view.filter)
442
+ })), columns);
443
+ const activeViewId = String(fieldSettings?.activeViewId || "").trim();
444
+ return {
445
+ hidden: mapList(fieldSettings?.hidden || []),
446
+ order: mapList(fieldSettings?.order || columns),
447
+ sort: mapSort(fieldSettings?.sort || []),
448
+ filter: mapFilter(fieldSettings?.filter),
449
+ types,
450
+ views,
451
+ activeViewId: views.some((view) => view.id === activeViewId) ? activeViewId : "",
452
+ favorite: Boolean(fieldSettings?.favorite)
453
+ };
454
+ }
455
+
456
+ function renameRowFields(row, nextColumns, renameMap = {}) {
457
+ const reverseMap = Object.entries(renameMap).reduce((acc, [previous, next]) => {
458
+ acc[next] = previous;
459
+ return acc;
460
+ }, {});
461
+ return nextColumns.reduce((acc, column) => {
462
+ const previousKey = reverseMap[column] || column;
463
+ acc[column] = row?.[previousKey] ?? row?.[column] ?? "";
464
+ return acc;
465
+ }, {});
466
+ }
467
+
468
+ function transformTableSchema(workspaceConfig, table, { columns, renameMap = {} }) {
469
+ const nextColumns = Array.isArray(columns) ? columns.filter(Boolean) : table.columns;
470
+ if (!nextColumns.length) return workspaceConfig;
471
+
472
+ if (table.storage === "manual-object") {
473
+ const objects = normalizeManualObjects(workspaceConfig);
474
+ const dataModel = workspaceConfig.dataModel && typeof workspaceConfig.dataModel === "object" && !Array.isArray(workspaceConfig.dataModel)
475
+ ? workspaceConfig.dataModel
476
+ : {};
477
+ return {
478
+ ...workspaceConfig,
479
+ dataModel: {
480
+ ...dataModel,
481
+ objects: objects.map((object) => {
482
+ if (object.id !== table.objectId) return object;
483
+ const rows = (Array.isArray(object.rows) ? object.rows : []).map((row) => renameRowFields(row, nextColumns, renameMap));
484
+ return {
485
+ ...object,
486
+ columns: nextColumns,
487
+ rows,
488
+ fieldSettings: remapFieldSettings(object.fieldSettings || {}, nextColumns, renameMap)
489
+ };
490
+ })
491
+ }
492
+ };
493
+ }
494
+
495
+ const ids = new Set((table.widgetRefs || []).map((ref) => ref.widgetId));
496
+ const mutateWidgets = (widgets) => (widgets || []).map((widget) => {
497
+ if (!ids.has(widget.id)) return widget;
498
+ const current = deriveWidgetTable(widget, { widgetId: widget.id });
499
+ if (!current?.mutable) return widget;
500
+ const rows = (current.rows || []).map((row) => renameRowFields(row, nextColumns, renameMap));
501
+ const nextFieldSettings = remapFieldSettings({
502
+ ...(widget.config?.fieldSettings || {}),
503
+ sort: widget.config?.sort,
504
+ filter: widget.config?.filter
505
+ }, nextColumns, renameMap);
506
+ return {
507
+ ...widget,
508
+ config: {
509
+ ...writeTableConfig(widget.config || {}, current.storage, nextColumns, rows),
510
+ fieldSettings: nextFieldSettings,
511
+ sort: nextFieldSettings.sort,
512
+ filter: nextFieldSettings.filter
513
+ }
514
+ };
515
+ });
516
+
517
+ const dashboards = (workspaceConfig.dashboards || []).map((dashboard) => ({
518
+ ...dashboard,
519
+ tabs: (dashboard.tabs || []).map((tab) => ({ ...tab, widgets: mutateWidgets(tab.widgets) }))
520
+ }));
521
+ let canvas = workspaceConfig.canvas ? { ...workspaceConfig.canvas } : {};
522
+ if (Array.isArray(canvas.widgets)) canvas = { ...canvas, widgets: mutateWidgets(canvas.widgets) };
523
+ if (Array.isArray(canvas.tabs)) canvas = { ...canvas, tabs: canvas.tabs.map((tab) => ({ ...tab, widgets: mutateWidgets(tab.widgets) })) };
524
+ return { ...workspaceConfig, dashboards, canvas };
525
+ }
526
+
257
527
  function applyTableMutation(workspaceConfig, table, mutate) {
258
528
  if (table.storage === "manual-object") {
259
529
  const objects = normalizeManualObjects(workspaceConfig);
@@ -330,6 +600,16 @@ function uniqueObjectId(workspaceConfig, name) {
330
600
  * targetObjectType:string, // objectType of the referenced object
331
601
  * type: "belongs-to" | "has-many",
332
602
  * description: string
603
+ * valueField?: string, // column on TARGET row used as stored FK value (default integrationId)
604
+ * labelField?: string, // primary label column on target (default Name)
605
+ * secondaryLabelField?: string,
606
+ * statusField?: string, // default status
607
+ * statusAllowlist?: string[] // when set, only rows whose status matches (case-insensitive) appear
608
+ * searchable?: boolean,
609
+ * pageSize?: number,
610
+ * resolver?: { integrationId: string } // optional listEntities-backed option source
611
+ * referenceSource?: "workspace-rows" | "source-records" // default workspace-rows
612
+ * sidecarSourceId?: string // when referenceSource is source-records
333
613
  * }
334
614
  */
335
615
  const OBJECT_TYPE_PRESETS = {
@@ -337,7 +617,20 @@ const OBJECT_TYPE_PRESETS = {
337
617
  label: "Data Source",
338
618
  icon: "Globe",
339
619
  description: "Custom API, webhook, or external feed. References an API Registry record while credentials stay in workspace settings.",
340
- columns: ["Name", "registryId", "endpoint", "authRef", "baseUrl", "status", "lastTested", "lastResponse"],
620
+ columns: [
621
+ "Name",
622
+ "registryId",
623
+ "endpoint",
624
+ "authRef",
625
+ "baseUrl",
626
+ "status",
627
+ "lastTested",
628
+ "lastResponse",
629
+ "entityType",
630
+ "sourceId",
631
+ "sourceStorage",
632
+ "resolverTemplateId"
633
+ ],
341
634
  relations: [
342
635
  {
343
636
  id: "resolver-binding",
@@ -345,7 +638,14 @@ const OBJECT_TYPE_PRESETS = {
345
638
  field: "registryId",
346
639
  targetObjectType: "api-registry",
347
640
  type: "belongs-to",
348
- description: "The API Registry entry whose fetchRecords function resolves this source. Set registryId to match the resolver integrationId."
641
+ description: "The API Registry entry whose fetchRecords function resolves this source. Set registryId to match the resolver integrationId.",
642
+ valueField: "integrationId",
643
+ labelField: "Name",
644
+ secondaryLabelField: "endpoint",
645
+ statusField: "status",
646
+ statusAllowlist: null,
647
+ searchable: true,
648
+ pageSize: 25
349
649
  }
350
650
  ]
351
651
  },
@@ -353,7 +653,23 @@ const OBJECT_TYPE_PRESETS = {
353
653
  label: "API Registry",
354
654
  icon: "Code2",
355
655
  description: "HTTP API records with endpoint config, auth references, connection status, and stored test output.",
356
- columns: ["integrationId", "authRef", "baseUrl", "endpoint", "method", "status", "lastTested", "lastResponse", "entityTypes", "description"],
656
+ columns: [
657
+ "integrationId",
658
+ "authRef",
659
+ "baseUrl",
660
+ "endpoint",
661
+ "method",
662
+ "status",
663
+ "lastTested",
664
+ "lastResponse",
665
+ "entityTypes",
666
+ "description",
667
+ "connectorKind",
668
+ "resolverTemplateId",
669
+ "schemaVersion",
670
+ "capabilities",
671
+ "executionLane"
672
+ ],
357
673
  relations: []
358
674
  },
359
675
  "people": {
@@ -383,6 +699,9 @@ const OBJECT_TYPE_PRESETS = {
383
699
  "runtime",
384
700
  "adapter",
385
701
  "agentHost",
702
+ "localModel",
703
+ "localEndpoint",
704
+ "intelligenceAdapterMode",
386
705
  "envRefs",
387
706
  "networkAllow",
388
707
  "allowList",
@@ -393,7 +712,10 @@ const OBJECT_TYPE_PRESETS = {
393
712
  "lastTested",
394
713
  "lastRunId",
395
714
  "lastSourceId",
396
- "lastResponse"
715
+ "lastResponse",
716
+ "resolverTemplateId",
717
+ "connectorKind",
718
+ "executionLane"
397
719
  ],
398
720
  relations: [
399
721
  {
@@ -402,7 +724,14 @@ const OBJECT_TYPE_PRESETS = {
402
724
  field: "schedulerRegistryId",
403
725
  targetObjectType: "api-registry",
404
726
  type: "belongs-to",
405
- description: "When runLocality is serverless, POST /api/workspace/sandbox-run sends growthub-sandbox-run-v1 to this API Registry record (METHOD, baseUrl, endpoint, authRef resolved server-side). Use for Supabase Edge URL, QStash forwarder, Vercel-exposed webhook, cron targets, etc."
727
+ description: "When runLocality is serverless, POST /api/workspace/sandbox-run sends growthub-sandbox-run-v1 to this API Registry record (METHOD, baseUrl, endpoint, authRef resolved server-side). Use for Supabase Edge URL, QStash forwarder, Vercel-exposed webhook, cron targets, etc.",
728
+ valueField: "integrationId",
729
+ labelField: "Name",
730
+ secondaryLabelField: "endpoint",
731
+ statusField: "status",
732
+ statusAllowlist: ["connected", "approved", "ok", "success"],
733
+ searchable: true,
734
+ pageSize: 25
406
735
  }
407
736
  ]
408
737
  },
@@ -440,7 +769,7 @@ function createTypedBusinessObject(workspaceConfig, { name, objectType = "custom
440
769
  rows: [],
441
770
  binding: { mode: "manual", source: "Data Model" },
442
771
  relations: preset.relations ? preset.relations.map((r) => ({ ...r })) : [],
443
- fieldSettings: { hidden: [], order: columns }
772
+ fieldSettings: normalizeFieldSettings({}, columns)
444
773
  };
445
774
  return {
446
775
  ...workspaceConfig,
@@ -468,7 +797,7 @@ function createManualBusinessObject(workspaceConfig, { name, fields } = {}) {
468
797
  columns,
469
798
  rows: [],
470
799
  binding: { mode: "manual", source: "Data Model" },
471
- fieldSettings: { hidden: [], order: columns }
800
+ fieldSettings: normalizeFieldSettings({}, columns)
472
801
  };
473
802
  return {
474
803
  ...workspaceConfig,
@@ -479,13 +808,22 @@ function createManualBusinessObject(workspaceConfig, { name, fields } = {}) {
479
808
  };
480
809
  }
481
810
 
482
- function addTableField(workspaceConfig, table, fieldName) {
483
- const name = String(fieldName || "").trim();
811
+ function addTableField(workspaceConfig, table, fieldSpec) {
812
+ const spec = fieldSpec && typeof fieldSpec === "object" && !Array.isArray(fieldSpec)
813
+ ? fieldSpec
814
+ : { name: fieldSpec };
815
+ const name = String(spec.name || "").trim();
816
+ const fieldType = String(spec.type || "").trim();
484
817
  if (!name || !table.mutable) return workspaceConfig;
485
- return applyTableMutation(workspaceConfig, table, ({ columns, rows }) => {
818
+ const nextConfig = applyTableMutation(workspaceConfig, table, ({ columns, rows }) => {
486
819
  if (columns.includes(name)) return { columns, rows };
487
820
  return { columns: [...columns, name], rows: rows.map((row) => ({ ...row, [name]: "" })) };
488
821
  });
822
+ return updateTableFieldSettings(nextConfig, { ...table, columns: table.columns.includes(name) ? table.columns : [...table.columns, name] }, (settings) => ({
823
+ ...settings,
824
+ order: settings.order.includes(name) ? settings.order : [...settings.order, name],
825
+ types: fieldType ? { ...(settings.types || {}), [name]: fieldType } : settings.types
826
+ }));
489
827
  }
490
828
 
491
829
  function addTableRow(workspaceConfig, table) {
@@ -621,6 +959,169 @@ function describeBindingMode(binding) {
621
959
  return { label: "Manual local table", description: "Rows and fields live in the existing widget config and travel with workspace export/import." };
622
960
  }
623
961
 
962
+ /**
963
+ * Normalize a reference option for API/UI interchange.
964
+ */
965
+ function normalizeReferenceOption(option) {
966
+ if (!option || typeof option !== "object") return null;
967
+ const value = String(option.value ?? "").trim();
968
+ if (!value) return null;
969
+ const source = ["workspace-config", "source-records", "resolver"].includes(option.source)
970
+ ? option.source
971
+ : "workspace-config";
972
+ const out = {
973
+ value,
974
+ label: String(option.label ?? value).trim() || value,
975
+ source,
976
+ objectType: typeof option.objectType === "string" && option.objectType.trim() ? option.objectType.trim() : undefined,
977
+ provider: typeof option.provider === "string" && option.provider.trim() ? option.provider.trim() : undefined,
978
+ status: typeof option.status === "string" && option.status.trim() ? option.status.trim() : undefined
979
+ };
980
+ if (option.secondaryLabel !== undefined && option.secondaryLabel !== null && String(option.secondaryLabel).trim()) {
981
+ out.secondaryLabel = String(option.secondaryLabel).trim();
982
+ }
983
+ if (option.metadata && typeof option.metadata === "object" && !Array.isArray(option.metadata)) {
984
+ out.metadata = option.metadata;
985
+ }
986
+ return out;
987
+ }
988
+
989
+ /**
990
+ * Merge preset relation defaults with stored `object.relations[]` so older rows
991
+ * pick up new optional metadata (valueField, statusAllowlist, …).
992
+ */
993
+ function effectiveRelations(object) {
994
+ const stored = Array.isArray(object?.relations) ? object.relations : [];
995
+ const presets =
996
+ object?.objectType && OBJECT_TYPE_PRESETS[object.objectType]?.relations
997
+ ? OBJECT_TYPE_PRESETS[object.objectType].relations
998
+ : [];
999
+ const presetFields = new Set(presets.map((p) => p.field).filter(Boolean));
1000
+ const mergedByField = new Map();
1001
+ for (const preset of presets) {
1002
+ if (!preset?.field) continue;
1003
+ const storedMatch = stored.find((s) => s?.field === preset.field);
1004
+ mergedByField.set(preset.field, { ...preset, ...(storedMatch || {}) });
1005
+ }
1006
+ const extras = stored.filter((s) => s?.field && !presetFields.has(s.field));
1007
+ return [...Array.from(mergedByField.values()), ...extras];
1008
+ }
1009
+
1010
+ function findRelationForField(object, field) {
1011
+ if (!field) return null;
1012
+ return effectiveRelations(object).find((r) => r.field === field) || null;
1013
+ }
1014
+
1015
+ function listReferenceFields(object) {
1016
+ return effectiveRelations(object).map((r) => r.field).filter(Boolean);
1017
+ }
1018
+
1019
+ function decodeRefCursor(cursor) {
1020
+ if (typeof cursor !== "string" || !cursor.startsWith("o:")) return 0;
1021
+ const n = Number(cursor.slice(2));
1022
+ return Number.isFinite(n) && n >= 0 ? n : 0;
1023
+ }
1024
+
1025
+ function encodeRefCursor(offset) {
1026
+ return `o:${offset}`;
1027
+ }
1028
+
1029
+ /**
1030
+ * Resolve reference options from local `dataModel.objects[]` rows (workspace-config source).
1031
+ */
1032
+ function resolveLocalReferenceOptions(workspaceConfig, {
1033
+ objectId,
1034
+ field,
1035
+ query = "",
1036
+ cursor = null,
1037
+ limit = 25,
1038
+ relation: relationOverride = null
1039
+ } = {}) {
1040
+ const objects = normalizeManualObjects(workspaceConfig);
1041
+ const objectItem = objects.find((o) => o.id === objectId) || null;
1042
+ const relation = relationOverride || (objectItem && field ? findRelationForField(objectItem, field) : null);
1043
+ if (!relation || !relation.targetObjectType) {
1044
+ return { options: [], nextCursor: null, reason: objectItem ? "unknown-field" : "unknown-object" };
1045
+ }
1046
+
1047
+ const valueField = typeof relation.valueField === "string" && relation.valueField.trim()
1048
+ ? relation.valueField.trim()
1049
+ : "integrationId";
1050
+ const labelField = typeof relation.labelField === "string" && relation.labelField.trim()
1051
+ ? relation.labelField.trim()
1052
+ : "Name";
1053
+ const secondaryLabelField =
1054
+ typeof relation.secondaryLabelField === "string" && relation.secondaryLabelField.trim()
1055
+ ? relation.secondaryLabelField.trim()
1056
+ : "";
1057
+ const statusField =
1058
+ typeof relation.statusField === "string" && relation.statusField.trim()
1059
+ ? relation.statusField.trim()
1060
+ : "status";
1061
+ const allowlist = Array.isArray(relation.statusAllowlist)
1062
+ ? relation.statusAllowlist.map((s) => String(s).toLowerCase())
1063
+ : null;
1064
+
1065
+ const pageSize = Math.min(100, Math.max(1, Number(relation.pageSize) || Number(limit) || 25));
1066
+ const offset = decodeRefCursor(cursor);
1067
+
1068
+ const targetObjectId = typeof relation.targetObjectId === "string" && relation.targetObjectId.trim()
1069
+ ? relation.targetObjectId.trim()
1070
+ : "";
1071
+ const targets = objects.filter((o) => (
1072
+ o.objectType === relation.targetObjectType
1073
+ && (!targetObjectId || o.id === targetObjectId)
1074
+ ));
1075
+ const needle = String(query || "").trim().toLowerCase();
1076
+
1077
+ const candidates = [];
1078
+ for (const target of targets) {
1079
+ const rows = Array.isArray(target.rows) ? target.rows : [];
1080
+ rows.forEach((row, index) => {
1081
+ if (!row || typeof row !== "object") return;
1082
+ const rawValue =
1083
+ row[valueField] ??
1084
+ row.integrationId ??
1085
+ row.id ??
1086
+ row.Name ??
1087
+ `${target.id}:${index}`;
1088
+ const value = String(rawValue ?? "").trim();
1089
+ if (!value) return;
1090
+ const label = String(row[labelField] ?? row.Name ?? row.integrationId ?? value).trim() || value;
1091
+ const secondaryLabel = secondaryLabelField
1092
+ ? String(row[secondaryLabelField] ?? "").trim()
1093
+ : "";
1094
+ const status = String(row[statusField] ?? "").trim();
1095
+ if (allowlist && allowlist.length) {
1096
+ const st = status.toLowerCase();
1097
+ if (!st || !allowlist.includes(st)) return;
1098
+ }
1099
+ if (needle) {
1100
+ const hay = `${value} ${label} ${secondaryLabel} ${status}`.toLowerCase();
1101
+ if (!hay.includes(needle)) return;
1102
+ }
1103
+ candidates.push(
1104
+ normalizeReferenceOption({
1105
+ value,
1106
+ label,
1107
+ secondaryLabel: secondaryLabel || undefined,
1108
+ source: "workspace-config",
1109
+ objectType: relation.targetObjectType,
1110
+ status: status || undefined,
1111
+ metadata: { objectLabel: target.label || target.source }
1112
+ })
1113
+ );
1114
+ });
1115
+ }
1116
+
1117
+ const filtered = candidates.filter(Boolean);
1118
+ const page = filtered.slice(offset, offset + pageSize);
1119
+ const nextOffset = offset + page.length;
1120
+ const nextCursor = nextOffset < filtered.length ? encodeRefCursor(nextOffset) : null;
1121
+
1122
+ return { options: page, nextCursor, reason: null, total: filtered.length };
1123
+ }
1124
+
624
1125
  export {
625
1126
  OBJECT_TYPE_PRESETS,
626
1127
  addTableField,
@@ -632,13 +1133,23 @@ export {
632
1133
  describeBindingLane,
633
1134
  describeBindingMode,
634
1135
  duplicateTableRow,
1136
+ effectiveRelations,
635
1137
  exportTableAsCsv,
1138
+ findRelationForField,
636
1139
  importTableFromCsv,
1140
+ listReferenceFields,
637
1141
  listSavedEnvRefs,
638
1142
  listWorkspaceDataModelTables,
1143
+ normalizeManualObjects,
1144
+ normalizeReferenceOption,
639
1145
  parseSandboxAllowList,
640
1146
  parseSandboxEnvRefs,
641
1147
  replaceTableContent,
1148
+ resolveLocalReferenceOptions,
642
1149
  sandboxRunSourceId,
1150
+ snapshotTableViewState,
1151
+ transformTableSchema,
1152
+ normalizeFieldSettings,
1153
+ updateTableFieldSettings,
643
1154
  updateTableCell
644
1155
  };