@growthub/cli 0.13.4 → 0.13.6

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 (52) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/QUICKSTART.md +19 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/.env.example +8 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +4 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/action/execute/route.js +60 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/actions/route.js +50 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/connect-session/route.js +68 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/connection-status/route.js +56 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/proxy/route.js +67 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/status/route.js +50 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +184 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +25 -2
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +161 -50
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +496 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +88 -1
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +41 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +120 -17
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/nango/page.jsx +167 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +1 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +67 -11
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +31 -10
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +16 -14
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/env.js +7 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/index.js +38 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-adapter.js +552 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-config-loader.js +202 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-schema.js +303 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +49 -10
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +1 -1
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/nango.js +49 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +4 -2
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +14 -1
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +218 -7
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +28 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +43 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +3 -1
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +36 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +53 -0
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +2 -1
  43. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
  44. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
  45. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
  46. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +102 -3
  47. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
  48. package/assets/worker-kits/growthub-custom-workspace-starter-v1/bundles/growthub-custom-workspace-starter-v1.json +1 -0
  49. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +7 -0
  50. package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +276 -0
  51. package/dist/index.js +127 -44
  52. package/package.json +1 -1
@@ -10,6 +10,25 @@ This kit is the canonical v1 starter primitive. Run `growthub starter init --nam
10
10
 
11
11
  After that, every customization you make stays yours — the Self-Healing Fork Sync Agent will propose upstream additions non-destructively, honouring your policy.
12
12
 
13
+ ## Official workspace paths
14
+
15
+ Workspace 1 stays the blank governed workspace:
16
+
17
+ ```bash
18
+ growthub starter init --name "My Workspace" --out ./my-workspace
19
+ ```
20
+
21
+ Workspace 2 is the opinionated project-management template path. It builds on the same app and adapter surface, but seeds API Registry, Data Source, Sandbox Environment workflow, and dashboard placeholders for project task deltas. It stores no provider secrets, OAuth connection ids, or provider task data.
22
+
23
+ ```bash
24
+ growthub starter init \
25
+ --name "Project Management Workspace" \
26
+ --out ./project-management-workspace \
27
+ --seed-config project-management
28
+ ```
29
+
30
+ After export, set `NANGO_SECRET_KEY` in the workspace runtime, complete provider OAuth, then fill `providerConfigKey`, `connectionIds`, and project identifiers in the Data Model before running the workflow.
31
+
13
32
  ## Run the bundled Vite UI shell
14
33
 
15
34
  ```bash
@@ -10,6 +10,14 @@ GROWTHUB_WORKSPACE_PAYMENT_ADAPTER=none
10
10
  # - static: local starter catalog only
11
11
  GROWTHUB_WORKSPACE_INTEGRATION_ADAPTER=growthub-bridge
12
12
 
13
+ # Nango thin adapter — row-scoped, not a workspace-wide adapter.
14
+ # Set NANGO_SECRET_KEY when any api-registry row has connectorKind: "nango".
15
+ # Cloud mode reaches Nango Cloud; self-hosted requires NANGO_HOST_URL.
16
+ NANGO_MODE=cloud
17
+ NANGO_HOST_URL=
18
+ NANGO_ENVIRONMENT=dev
19
+ NANGO_SECRET_KEY=
20
+
13
21
  # Hosted bridge authority
14
22
  GROWTHUB_BRIDGE_BASE_URL=https://www.growthub.ai
15
23
  GROWTHUB_BRIDGE_INTEGRATIONS_PATH=/api/mcp/accounts
@@ -13,6 +13,10 @@ It intentionally depends on adapter contracts:
13
13
  - `GROWTHUB_BRIDGE_ACCESS_TOKEN`
14
14
  - `GROWTHUB_BRIDGE_USER_ID`
15
15
  - `GROWTHUB_WORKSPACE_BYO_CONNECTIONS_JSON`
16
+ - `NANGO_SECRET_KEY` (required when any api-registry row uses `connectorKind: "nango"`)
17
+ - `NANGO_HOST_URL` (self-hosted Nango only)
18
+ - `NANGO_ENVIRONMENT` (default `dev`)
19
+ - `NANGO_MODE` (`cloud` | `self-hosted`, default `cloud`)
16
20
 
17
21
  The Growthub local-first operator shell remains at `../../studio`.
18
22
 
@@ -0,0 +1,60 @@
1
+ /**
2
+ * POST /api/workspace/integrations/nango/action/execute
3
+ *
4
+ * Executes a declared Nango action function against the connection
5
+ * identified by `providerConfigKey` + `connectionId`. Used by workspace
6
+ * agents as a tool-call surface.
7
+ *
8
+ * Request body:
9
+ * {
10
+ * providerConfigKey: string,
11
+ * connectionId: string,
12
+ * action: string, // action function name from nango.yaml
13
+ * input?: object | array // arguments to the action
14
+ * }
15
+ *
16
+ * Response — success: 200 { ok: true, action, providerConfigKey, connectionId, result, environment }
17
+ * Response — validation failure: 400 { ok: false, error, details }
18
+ * Response — Nango not configured: 503 { ok: false, error, code }
19
+ * Response — upstream failure: 502 { ok: false, error }
20
+ */
21
+
22
+ import { NextResponse } from "next/server";
23
+ import { executeAction, validateActionExecuteRequest } from "@/lib/adapters/integrations/nango";
24
+
25
+ async function POST(request) {
26
+ let body;
27
+ try {
28
+ body = await request.json();
29
+ } catch {
30
+ return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
31
+ }
32
+
33
+ let validated;
34
+ try {
35
+ validated = validateActionExecuteRequest(body);
36
+ } catch (error) {
37
+ return NextResponse.json(
38
+ { ok: false, error: error.message, details: error.details || null },
39
+ { status: 400 }
40
+ );
41
+ }
42
+
43
+ try {
44
+ const result = await executeAction(validated);
45
+ return NextResponse.json({ ok: true, ...result });
46
+ } catch (error) {
47
+ if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE" || error.code === "NANGO_SDK_SHAPE") {
48
+ return NextResponse.json(
49
+ { ok: false, error: error.message, code: error.code },
50
+ { status: 503 }
51
+ );
52
+ }
53
+ return NextResponse.json(
54
+ { ok: false, error: error?.message || "nango action execution failed" },
55
+ { status: 502 }
56
+ );
57
+ }
58
+ }
59
+
60
+ export { POST };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * GET /api/workspace/integrations/nango/actions
3
+ *
4
+ * Lists the actions declared in `nango.yaml` for a given provider config key
5
+ * (or, when no key is provided, returns the full discoverable set). The
6
+ * response shape is MCP-compatible: each action exposes a name and (when
7
+ * provided by Nango) an input/output schema.
8
+ *
9
+ * Query parameters (optional):
10
+ * - providerConfigKey filter to actions for a single provider
11
+ *
12
+ * Response shape:
13
+ * { ok: true, providerConfigKey, probedShape, actions: Array, hint: string|null }
14
+ */
15
+
16
+ import { NextResponse } from "next/server";
17
+ import { listActions, validateActionsListInput } from "@/lib/adapters/integrations/nango";
18
+
19
+ async function GET(request) {
20
+ const url = new URL(request.url);
21
+ const raw = {
22
+ providerConfigKey: url.searchParams.get("providerConfigKey") || undefined
23
+ };
24
+ let input;
25
+ try {
26
+ input = validateActionsListInput(raw);
27
+ } catch (error) {
28
+ return NextResponse.json(
29
+ { ok: false, error: error.message, details: error.details || null },
30
+ { status: 400 }
31
+ );
32
+ }
33
+ try {
34
+ const result = await listActions(input);
35
+ return NextResponse.json({ ok: true, ...result });
36
+ } catch (error) {
37
+ if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE") {
38
+ return NextResponse.json(
39
+ { ok: false, error: error.message, code: error.code },
40
+ { status: 503 }
41
+ );
42
+ }
43
+ return NextResponse.json(
44
+ { ok: false, error: error?.message || "nango actions list failed" },
45
+ { status: 502 }
46
+ );
47
+ }
48
+ }
49
+
50
+ export { GET };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * POST /api/workspace/integrations/nango/connect-session
3
+ *
4
+ * Creates a Nango Connect Session for the given providerConfigKey and
5
+ * returns a short-lived connect_link the browser opens to start OAuth.
6
+ * The Nango Connect UI mints tokens directly against Nango — the workspace
7
+ * never sees the resulting OAuth credentials. The token returned here is a
8
+ * Connect Session token (handoff only), not a provider credential.
9
+ *
10
+ * Lifecycle: Nango creates the connectionId AFTER the user completes OAuth
11
+ * and delivers it through the auth webhook. A normal Connect Session
12
+ * therefore does NOT need a connectionId as input — only the explicit
13
+ * Reconnect path (`reconnect: true`) operates on an existing connectionId.
14
+ *
15
+ * Request body:
16
+ * {
17
+ * providerConfigKey: string, // required, alphanumeric (+ _.-) <= 64 chars
18
+ * reconnect?: boolean, // true → use the SDK's reconnect-session path
19
+ * connectionId?: string, // REQUIRED when reconnect=true; ignored for a normal Create Connect Session
20
+ * endUser?: { id?: string, email?: string },
21
+ * tags?: Record<string, string> // echoed back in the Nango auth webhook so the workspace can map the eventual connectionId to the originating row
22
+ * }
23
+ *
24
+ * Response — success: 200 { ok: true, providerConfigKey, environment, mode, token, connectLink, sdkMethod, tagsEchoed }
25
+ * Response — validation failure: 400 { ok: false, error, details }
26
+ * Response — Nango not configured / SDK missing: 503 { ok: false, error, code }
27
+ * Response — upstream failure: 502 { ok: false, error }
28
+ */
29
+
30
+ import { NextResponse } from "next/server";
31
+ import { createConnectSession, validateConnectSessionRequest } from "@/lib/adapters/integrations/nango";
32
+
33
+ async function POST(request) {
34
+ let body;
35
+ try {
36
+ body = await request.json();
37
+ } catch {
38
+ return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
39
+ }
40
+
41
+ let validated;
42
+ try {
43
+ validated = validateConnectSessionRequest(body);
44
+ } catch (error) {
45
+ return NextResponse.json(
46
+ { ok: false, error: error.message, details: error.details || null },
47
+ { status: 400 }
48
+ );
49
+ }
50
+
51
+ try {
52
+ const result = await createConnectSession(validated);
53
+ return NextResponse.json({ ok: true, ...result });
54
+ } catch (error) {
55
+ if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE" || error.code === "NANGO_SDK_SHAPE") {
56
+ return NextResponse.json(
57
+ { ok: false, error: error.message, code: error.code },
58
+ { status: 503 }
59
+ );
60
+ }
61
+ return NextResponse.json(
62
+ { ok: false, error: error?.message || "nango connect session failed" },
63
+ { status: 502 }
64
+ );
65
+ }
66
+ }
67
+
68
+ export { POST };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * POST /api/workspace/integrations/nango/connection-status
3
+ *
4
+ * Returns the safe (non-credential) status summary for a specific Nango
5
+ * connection. Used by the NangoConnectionPanel sidecar to verify a
6
+ * per-row connection without exposing OAuth tokens to the browser.
7
+ *
8
+ * Request body:
9
+ * { providerConfigKey: string, connectionId: string }
10
+ *
11
+ * Response — connected: 200 { ok: true, status: "connected", providerConfigKey, connectionId, environment, connection: { ...safe fields... } }
12
+ * Response — not yet connected: 200 { ok: true, status: "not-connected", providerConfigKey, connectionId, environment, reason }
13
+ * Response — validation failure: 400 { ok: false, error, details }
14
+ * Response — Nango not configured: 503 { ok: false, error, code }
15
+ * Response — upstream failure: 502 { ok: false, error }
16
+ */
17
+
18
+ import { NextResponse } from "next/server";
19
+ import { getConnectionSummary, validateConnectionSummaryRequest } from "@/lib/adapters/integrations/nango";
20
+
21
+ async function POST(request) {
22
+ let body;
23
+ try {
24
+ body = await request.json();
25
+ } catch {
26
+ return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
27
+ }
28
+
29
+ let validated;
30
+ try {
31
+ validated = validateConnectionSummaryRequest(body);
32
+ } catch (error) {
33
+ return NextResponse.json(
34
+ { ok: false, error: error.message, details: error.details || null },
35
+ { status: 400 }
36
+ );
37
+ }
38
+
39
+ try {
40
+ const result = await getConnectionSummary(validated);
41
+ return NextResponse.json({ ok: true, ...result });
42
+ } catch (error) {
43
+ if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE" || error.code === "NANGO_SDK_SHAPE") {
44
+ return NextResponse.json(
45
+ { ok: false, error: error.message, code: error.code },
46
+ { status: 503 }
47
+ );
48
+ }
49
+ return NextResponse.json(
50
+ { ok: false, error: error?.message || "nango connection status failed" },
51
+ { status: 502 }
52
+ );
53
+ }
54
+ }
55
+
56
+ export { POST };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * POST /api/workspace/integrations/nango/proxy
3
+ *
4
+ * Proxies an API request through Nango using the connection identified by
5
+ * `providerConfigKey` + `connectionId`. The Nango SDK injects credentials
6
+ * server-side; the browser never holds the upstream provider's auth.
7
+ *
8
+ * Request body:
9
+ * {
10
+ * providerConfigKey: string, // Nango integration config key
11
+ * connectionId: string, // Nango connection id
12
+ * method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
13
+ * endpoint: string, // path or absolute URL
14
+ * headers?: object, // forbidden: Authorization, X-API-Key, etc.
15
+ * params?: object,
16
+ * data?: any,
17
+ * retries?: number, // 0..10
18
+ * timeoutMs?: number // 0..60000
19
+ * }
20
+ *
21
+ * Response — success:
22
+ * { ok: true, status: number, data: any, environment: string }
23
+ *
24
+ * Response — validation failure: 400 { ok: false, error, details }
25
+ * Response — Nango not configured: 503 { ok: false, error, code }
26
+ * Response — upstream failure: 502 { ok: false, error }
27
+ */
28
+
29
+ import { NextResponse } from "next/server";
30
+ import { proxyRequest, validateProxyRequest } from "@/lib/adapters/integrations/nango";
31
+
32
+ async function POST(request) {
33
+ let body;
34
+ try {
35
+ body = await request.json();
36
+ } catch {
37
+ return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
38
+ }
39
+
40
+ let validated;
41
+ try {
42
+ validated = validateProxyRequest(body);
43
+ } catch (error) {
44
+ return NextResponse.json(
45
+ { ok: false, error: error.message, details: error.details || null },
46
+ { status: 400 }
47
+ );
48
+ }
49
+
50
+ try {
51
+ const result = await proxyRequest(validated);
52
+ return NextResponse.json({ ok: true, ...result });
53
+ } catch (error) {
54
+ if (error.code === "NANGO_NOT_CONFIGURED" || error.code === "NANGO_SDK_UNAVAILABLE" || error.code === "NANGO_SDK_SHAPE") {
55
+ return NextResponse.json(
56
+ { ok: false, error: error.message, code: error.code },
57
+ { status: 503 }
58
+ );
59
+ }
60
+ return NextResponse.json(
61
+ { ok: false, error: error?.message || "nango proxy failed" },
62
+ { status: 502 }
63
+ );
64
+ }
65
+ }
66
+
67
+ export { POST };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * GET /api/workspace/integrations/nango/status
3
+ *
4
+ * Returns Nango connection health for the no-code Integrations panel.
5
+ *
6
+ * Query parameters (all optional):
7
+ * - providerConfigKey probe a specific provider connection
8
+ * - connectionId probe a specific connection (paired with providerConfigKey)
9
+ * - mode override mode for the probe ("cloud" | "self-hosted")
10
+ * - hostUrl override host URL for self-hosted probes
11
+ *
12
+ * Response shape:
13
+ * { status: "connected" | "disconnected" | "unconfigured", ... }
14
+ *
15
+ * Authority contract: never returns the Nango secret key. Never echoes
16
+ * credential-shaped fields from upstream SDK responses.
17
+ */
18
+
19
+ import { NextResponse } from "next/server";
20
+ import { getStatus, validateConnectionStatusRequest } from "@/lib/adapters/integrations/nango";
21
+
22
+ async function GET(request) {
23
+ const url = new URL(request.url);
24
+ const raw = {
25
+ providerConfigKey: url.searchParams.get("providerConfigKey") || undefined,
26
+ connectionId: url.searchParams.get("connectionId") || undefined,
27
+ mode: url.searchParams.get("mode") || undefined,
28
+ hostUrl: url.searchParams.get("hostUrl") || undefined
29
+ };
30
+ let input;
31
+ try {
32
+ input = validateConnectionStatusRequest(raw);
33
+ } catch (error) {
34
+ return NextResponse.json(
35
+ { error: error.message, details: error.details || null },
36
+ { status: 400 }
37
+ );
38
+ }
39
+ try {
40
+ const result = await getStatus(input);
41
+ return NextResponse.json(result);
42
+ } catch (error) {
43
+ return NextResponse.json(
44
+ { error: error?.message || "nango status probe failed" },
45
+ { status: 500 }
46
+ );
47
+ }
48
+ }
49
+
50
+ export { GET };
@@ -0,0 +1,184 @@
1
+ /**
2
+ * GET /api/workspace/metadata-graph
3
+ *
4
+ * Growthub Workspace Metadata Graph V1 — read-only projection.
5
+ *
6
+ * Combines the governed workspace config and the live source-record sidecar
7
+ * into a typed metadata store and a node/edge graph. Consumers (the UI
8
+ * inspector, the workspace helper agent, and external operators) use this
9
+ * envelope to ask dependency questions without re-deriving widget/workflow
10
+ * contracts inside every component.
11
+ *
12
+ * Optional query parameters:
13
+ * - staleKind: "object" | "field" | "sourceRecord" | "workflow" | "agentHost" | "widget"
14
+ * - staleId: the corresponding metadata id (for `field`, use
15
+ * "<objectId>::<fieldId>")
16
+ *
17
+ * When both are provided the response `stale.groups` and `stale.reasons`
18
+ * reflect `selectStaleMetadataGroups({ kind, id })`. When omitted the
19
+ * stale section returns the empty baseline.
20
+ *
21
+ * Authority invariants:
22
+ * - GET only. PATCH / POST / PUT / DELETE are not exposed. Writes still
23
+ * flow through the existing governed routes
24
+ * (`PATCH /api/workspace`, `POST /api/workspace/refresh-sources`,
25
+ * `POST /api/workspace/sandbox-run`).
26
+ * - growthub.config.json remains the authoritative artifact.
27
+ * - No secrets are returned. Field metadata derived from secret-shaped
28
+ * column names is marked `isSecret: true` but no value is echoed.
29
+ * - Failures during read OR projection fall back to an empty store with
30
+ * warnings — this route never throws.
31
+ */
32
+
33
+ import { NextResponse } from "next/server";
34
+ import { readWorkspaceConfig, readWorkspaceSourceRecords } from "@/lib/workspace-config";
35
+ import { buildWorkspaceMetadataStore } from "@/lib/workspace-metadata-store";
36
+ import { buildWorkspaceMetadataGraph } from "@/lib/workspace-metadata-graph";
37
+ import { selectStaleMetadataGroups } from "@/lib/workspace-metadata-selectors";
38
+
39
+ const ENVELOPE_KIND = "growthub-workspace-metadata-graph-v1";
40
+ const ENVELOPE_VERSION = 1;
41
+
42
+ function emptyMetadataStore() {
43
+ return {
44
+ kind: "growthub-workspace-metadata-store-v1",
45
+ version: 1,
46
+ objects: [],
47
+ fields: [],
48
+ views: [],
49
+ filters: [],
50
+ sorts: [],
51
+ widgets: [],
52
+ dashboards: [],
53
+ workflows: [],
54
+ workflowNodes: [],
55
+ workflowActions: [],
56
+ runInputs: [],
57
+ agentHosts: [],
58
+ sandboxes: [],
59
+ integrations: [],
60
+ integrationEntities: [],
61
+ sourceRecords: [],
62
+ runs: [],
63
+ outputArtifacts: [],
64
+ workerKits: [],
65
+ pipelineHealth: [],
66
+ warnings: []
67
+ };
68
+ }
69
+
70
+ async function GET(request) {
71
+ const warnings = [];
72
+
73
+ let workspaceConfig = null;
74
+ try {
75
+ workspaceConfig = await readWorkspaceConfig();
76
+ } catch (error) {
77
+ warnings.push(`Failed to read workspace config: ${error?.message || "unknown error"}`);
78
+ }
79
+
80
+ let workspaceSourceRecords = {};
81
+ try {
82
+ workspaceSourceRecords = (await readWorkspaceSourceRecords()) || {};
83
+ } catch (error) {
84
+ warnings.push(`Failed to read source records sidecar: ${error?.message || "unknown error"}`);
85
+ }
86
+
87
+ // Defensive: helpers are designed to never throw on partial/unknown input,
88
+ // but the route must remain HTTP-200 even if an unexpected exception bubbles
89
+ // up (so the UI inspector and agents always get a typed envelope).
90
+ let metadataStore;
91
+ try {
92
+ metadataStore = buildWorkspaceMetadataStore({
93
+ workspaceConfig: workspaceConfig || {},
94
+ workspaceSourceRecords
95
+ });
96
+ warnings.push(...metadataStore.warnings);
97
+ } catch (error) {
98
+ warnings.push(`Failed to build metadata store: ${error?.message || "unknown error"}`);
99
+ metadataStore = emptyMetadataStore();
100
+ }
101
+
102
+ let graph;
103
+ try {
104
+ graph = buildWorkspaceMetadataGraph(metadataStore);
105
+ warnings.push(...graph.warnings);
106
+ } catch (error) {
107
+ warnings.push(`Failed to build metadata graph: ${error?.message || "unknown error"}`);
108
+ graph = { kind: "growthub-workspace-metadata-graph-v1", version: 1, nodes: [], edges: [], warnings: [] };
109
+ }
110
+
111
+ // Optional stale-group selector via query params.
112
+ let staleGroups = [];
113
+ let staleReasons = [];
114
+ try {
115
+ const url = request && request.url ? new URL(request.url) : null;
116
+ const staleKind = url ? (url.searchParams.get("staleKind") || "").trim() : "";
117
+ const staleId = url ? (url.searchParams.get("staleId") || "").trim() : "";
118
+ if (staleKind && staleId) {
119
+ const result = selectStaleMetadataGroups(metadataStore, { kind: staleKind, id: staleId });
120
+ staleGroups = Array.isArray(result?.groups) ? result.groups : [];
121
+ staleReasons = Array.isArray(result?.reasons) ? result.reasons : [];
122
+ }
123
+ } catch (error) {
124
+ warnings.push(`Failed to compute stale groups: ${error?.message || "unknown error"}`);
125
+ }
126
+
127
+ return NextResponse.json({
128
+ kind: ENVELOPE_KIND,
129
+ version: ENVELOPE_VERSION,
130
+ authority: {
131
+ config: "growthub.config.json",
132
+ sourceRecords: "growthub.source-records.json",
133
+ readOnlyProjection: true
134
+ },
135
+ metadata: {
136
+ objects: metadataStore.objects,
137
+ fields: metadataStore.fields,
138
+ views: metadataStore.views,
139
+ filters: metadataStore.filters,
140
+ sorts: metadataStore.sorts,
141
+ widgets: metadataStore.widgets,
142
+ dashboards: metadataStore.dashboards,
143
+ workflows: metadataStore.workflows,
144
+ workflowNodes: metadataStore.workflowNodes,
145
+ workflowActions: metadataStore.workflowActions,
146
+ runInputs: metadataStore.runInputs,
147
+ agentHosts: metadataStore.agentHosts,
148
+ sandboxes: metadataStore.sandboxes,
149
+ integrations: metadataStore.integrations,
150
+ integrationEntities: metadataStore.integrationEntities,
151
+ sourceRecords: metadataStore.sourceRecords,
152
+ runs: metadataStore.runs,
153
+ outputArtifacts: metadataStore.outputArtifacts,
154
+ workerKits: metadataStore.workerKits,
155
+ pipelineHealth: metadataStore.pipelineHealth
156
+ },
157
+ graph: {
158
+ nodes: graph.nodes,
159
+ edges: graph.edges
160
+ },
161
+ stale: {
162
+ groups: staleGroups,
163
+ reasons: staleReasons
164
+ },
165
+ warnings,
166
+ selectors: {
167
+ // Manifest of selectors the route honours. Only `selectStaleMetadataGroups`
168
+ // is wired through HTTP (via `?staleKind=&staleId=`). The remaining
169
+ // selectors are exposed as importable helpers for server-side consumers
170
+ // and the read-only inspector; they are NOT toggled through query
171
+ // params in V1.
172
+ httpEnabled: ["selectStaleMetadataGroups"],
173
+ helperOnly: [
174
+ "selectWidgetRequiredFields",
175
+ "selectWorkflowNodeInputSchema",
176
+ "selectObjectFilterableFields",
177
+ "selectObjectSortableFields",
178
+ "selectRunLineage"
179
+ ]
180
+ }
181
+ });
182
+ }
183
+
184
+ export { GET };
@@ -392,6 +392,12 @@ function buildRunResponse({
392
392
  base.runInputs = runInputs;
393
393
  base.inputSummary = summarizeRunInputs(runInputs);
394
394
  }
395
+ if (result && typeof result === "object" && result.swarm && typeof result.swarm === "object") {
396
+ base.swarm = result.swarm;
397
+ }
398
+ if (result && typeof result === "object" && Array.isArray(result.logTree)) {
399
+ base.logTree = result.logTree;
400
+ }
395
401
  base.exports = {
396
402
  available: ["download-json", "copy-output", "download-stdout", "download-stderr", "download-log-node"],
397
403
  external: []
@@ -558,11 +564,28 @@ async function POST(request) {
558
564
  workspaceConfig,
559
565
  row: rowForRun,
560
566
  timeoutMs,
561
- runInputs: normalizedRunInputs
567
+ runInputs: normalizedRunInputs,
568
+ executionContext: {
569
+ runId,
570
+ ranAt,
571
+ runtime,
572
+ agentHost,
573
+ adapterId,
574
+ env,
575
+ envRefSlugs,
576
+ envRefsMissing,
577
+ envRefsResolved,
578
+ networkAllow,
579
+ allowList,
580
+ instructions,
581
+ command,
582
+ timeoutMs,
583
+ sandboxName: rowForRun.Name || name
584
+ }
562
585
  });
563
586
  if (graphResult !== null) {
564
587
  result = graphResult;
565
- effectiveAdapterId = "orchestration-graph";
588
+ effectiveAdapterId = String(graphResult?.adapterMeta?.adapter || "").trim() || "orchestration-graph";
566
589
  }
567
590
  }
568
591