@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.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +184 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +24 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +14 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +74 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +77 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +72 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +123 -27
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +224 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +754 -92
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +224 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +32 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +530 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +8 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +10 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/RunSetupPanel.jsx +261 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +119 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +779 -138
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +91 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +35 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +28 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +216 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +412 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +366 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +34 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-eligibility.js +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-redaction.js +64 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +665 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-host-catalog.js +168 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +595 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +164 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +11 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +111 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +14 -0
- package/package.json +1 -1
|
@@ -265,10 +265,152 @@ function normalizeFieldSettings(fieldSettings, columns) {
|
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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]}`
|