@growthub/cli 0.14.3 → 0.14.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/resolvers/[integrationId]/route.js +157 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/env-status/route.js +5 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +33 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolvers/route.js +86 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +30 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryReviewModal.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +532 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +400 -188
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +36 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxOrchestrationEditorPanel.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +9 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +14 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +24 -19
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-agent-teams.js +211 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-bootstrap-console.js +325 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-cockpit-console.js +206 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +7 -82
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/resolver-constructor.js +217 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-registry.js +99 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/unified-resolver-registry.js +545 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-policy.js +2 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +30 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +69 -0
- package/package.json +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +0 -141
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolConfirmModal.jsx +0 -64
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolDraftPanel.jsx +0 -376
|
@@ -78,21 +78,22 @@ import { isSandboxLocalAgentHost } from "@/lib/sandbox-agent-auth-eligibility";
|
|
|
78
78
|
import { StatusPill } from "./StatusPill.jsx";
|
|
79
79
|
import { SegmentedToggle, ToggleField } from "./ToggleField.jsx";
|
|
80
80
|
import { SourceTestPanel } from "./SourceTestPanel.jsx";
|
|
81
|
-
import { SandboxToolDraftPanel } from "./SandboxToolDraftPanel.jsx";
|
|
82
|
-
import { SandboxToolConfirmModal } from "./SandboxToolConfirmModal.jsx";
|
|
83
81
|
import { OrchestrationRunTracePanel } from "./OrchestrationRunTracePanel.jsx";
|
|
84
82
|
import { ApiRegistryCreationCockpit } from "./ApiRegistryCreationCockpit.jsx";
|
|
85
83
|
import { deriveApiRegistryCreationState } from "@/lib/api-registry-creation-flow";
|
|
86
84
|
import { deriveSandboxServerlessState } from "@/lib/sandbox-serverless-flow";
|
|
87
85
|
import { profileApiResponse, recommendResolver } from "@/lib/api-response-profile";
|
|
86
|
+
import { constructResolverProposal } from "@/lib/resolver-constructor";
|
|
88
87
|
import { classifyCreationError } from "@/lib/creation-error-recovery";
|
|
89
88
|
import {
|
|
90
|
-
|
|
89
|
+
buildDefaultOrchestrationGraphFromRegistry,
|
|
91
90
|
findSandboxRowsForRegistry,
|
|
92
91
|
buildDataSourceRowFromApiRegistry,
|
|
93
92
|
findDataSourceRowsForRegistry,
|
|
94
93
|
getOrchestrationGraphUiState,
|
|
95
|
-
redactSecretsFromText
|
|
94
|
+
redactSecretsFromText,
|
|
95
|
+
serializeOrchestrationGraph,
|
|
96
|
+
slugifyName
|
|
96
97
|
} from "@/lib/orchestration-graph";
|
|
97
98
|
import {
|
|
98
99
|
FIELD_TYPE_ICON_NAMES,
|
|
@@ -1228,17 +1229,17 @@ function DataModelRecordDrawer({
|
|
|
1228
1229
|
const [sandboxHistoryMessage, setSandboxHistoryMessage] = useState("");
|
|
1229
1230
|
const [loadingSandboxHistory, setLoadingSandboxHistory] = useState(false);
|
|
1230
1231
|
const [expandedJson, setExpandedJson] = useState(null);
|
|
1231
|
-
const [
|
|
1232
|
-
const [sandboxToolDraft, setSandboxToolDraft] = useState({});
|
|
1233
|
-
const [sandboxToolCreating, setSandboxToolCreating] = useState(false);
|
|
1234
|
-
const [createdSandboxMeta, setCreatedSandboxMeta] = useState(null);
|
|
1235
|
-
const [createdSandboxTesting, setCreatedSandboxTesting] = useState(false);
|
|
1236
|
-
const [createdSandboxTestMessage, setCreatedSandboxTestMessage] = useState("");
|
|
1232
|
+
const [creatingWorkflowCanvas, setCreatingWorkflowCanvas] = useState(false);
|
|
1237
1233
|
const [creatingDataSource, setCreatingDataSource] = useState(false);
|
|
1238
1234
|
const [createdDataSourceMeta, setCreatedDataSourceMeta] = useState(null);
|
|
1239
1235
|
const [dataSourceMessage, setDataSourceMessage] = useState("");
|
|
1240
1236
|
const [cockpitBusy, setCockpitBusy] = useState("");
|
|
1241
1237
|
const [cockpitCollapsed, setCockpitCollapsed] = useState(false);
|
|
1238
|
+
// CMS SDK v1.5.1 — a staged, constructed resolver awaiting one-screen review.
|
|
1239
|
+
// null until the operator clicks "Construct resolver"; cleared on apply/cancel.
|
|
1240
|
+
const [resolverConstruct, setResolverConstruct] = useState(null);
|
|
1241
|
+
const [resolverConstructBusy, setResolverConstructBusy] = useState(false);
|
|
1242
|
+
const [resolverConstructMessage, setResolverConstructMessage] = useState("");
|
|
1242
1243
|
// Real runtime truth for the creation cockpit: which auth refs resolve in the
|
|
1243
1244
|
// server runtime, and the live source-records sidecar. Fetched (never guessed)
|
|
1244
1245
|
// so auth/refresh readiness reflect actual state, and refreshed after actions.
|
|
@@ -1265,10 +1266,7 @@ function DataModelRecordDrawer({
|
|
|
1265
1266
|
setSandboxHistory([]);
|
|
1266
1267
|
setSandboxHistoryMessage("");
|
|
1267
1268
|
setExpandedJson(null);
|
|
1268
|
-
|
|
1269
|
-
setSandboxToolDraft({});
|
|
1270
|
-
setCreatedSandboxMeta(null);
|
|
1271
|
-
setCreatedSandboxTestMessage("");
|
|
1269
|
+
setCreatingWorkflowCanvas(false);
|
|
1272
1270
|
setCreatingDataSource(false);
|
|
1273
1271
|
setCreatedDataSourceMeta(null);
|
|
1274
1272
|
setDataSourceMessage("");
|
|
@@ -1423,7 +1421,17 @@ function DataModelRecordDrawer({
|
|
|
1423
1421
|
function ensureSandboxColumns(config, sandboxTable) {
|
|
1424
1422
|
let next = config;
|
|
1425
1423
|
let current = sandboxTable;
|
|
1426
|
-
for (const field of [
|
|
1424
|
+
for (const field of [
|
|
1425
|
+
"orchestrationDraftConfig",
|
|
1426
|
+
"orchestrationDraftStatus",
|
|
1427
|
+
"orchestrationDraftUpdatedAt",
|
|
1428
|
+
"orchestrationDraftBaseVersion",
|
|
1429
|
+
"orchestrationDraftTestPassed",
|
|
1430
|
+
"orchestrationDraftTestedConfig",
|
|
1431
|
+
"description",
|
|
1432
|
+
"connectorKind",
|
|
1433
|
+
"executionLane"
|
|
1434
|
+
]) {
|
|
1427
1435
|
if (!current.columns.includes(field)) {
|
|
1428
1436
|
next = addTableField(next, current, field);
|
|
1429
1437
|
const tables = listWorkspaceDataModelTables(next);
|
|
@@ -1433,59 +1441,216 @@ function DataModelRecordDrawer({
|
|
|
1433
1441
|
return { config: next, sandboxTable: current };
|
|
1434
1442
|
}
|
|
1435
1443
|
|
|
1436
|
-
function
|
|
1437
|
-
|
|
1444
|
+
function drawerId(prefix) {
|
|
1445
|
+
return `${prefix}-${Math.random().toString(36).slice(2, 8)}-${Date.now().toString(36)}`;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function addWorkflowFolderShortcut(dataModel, workflow) {
|
|
1449
|
+
const objects = Array.isArray(dataModel?.objects) ? dataModel.objects : [];
|
|
1450
|
+
const seededObjects = objects.some((object) => object?.id === "nav-folders")
|
|
1451
|
+
? objects
|
|
1452
|
+
: [
|
|
1453
|
+
...objects,
|
|
1454
|
+
{
|
|
1455
|
+
id: "nav-folders",
|
|
1456
|
+
label: "Custom Folders",
|
|
1457
|
+
source: "Custom Folders",
|
|
1458
|
+
objectType: "custom",
|
|
1459
|
+
icon: "Folder",
|
|
1460
|
+
columns: ["name", "order", "collapsed", "items"],
|
|
1461
|
+
rows: [],
|
|
1462
|
+
binding: { mode: "manual", source: "Custom Folders" }
|
|
1463
|
+
}
|
|
1464
|
+
];
|
|
1465
|
+
const navIndex = seededObjects.findIndex((object) => object?.id === "nav-folders");
|
|
1466
|
+
const navObject = seededObjects[navIndex];
|
|
1467
|
+
const rows = Array.isArray(navObject?.rows) ? navObject.rows : [];
|
|
1468
|
+
const folderName = "Builder";
|
|
1469
|
+
const item = {
|
|
1470
|
+
id: drawerId("item"),
|
|
1471
|
+
type: "workflow",
|
|
1472
|
+
objectId: workflow.objectId,
|
|
1473
|
+
rowId: workflow.rowId,
|
|
1474
|
+
fieldName: "orchestrationConfig",
|
|
1475
|
+
label: workflow.label,
|
|
1476
|
+
builderManaged: true,
|
|
1477
|
+
icon: "GitBranch",
|
|
1478
|
+
color: "#111827",
|
|
1479
|
+
iconBg: "#f3f4f6"
|
|
1480
|
+
};
|
|
1481
|
+
const existingFolder = rows.find((row) => String(row?.name || "").trim().toLowerCase() === folderName.toLowerCase());
|
|
1482
|
+
const nextRows = existingFolder
|
|
1483
|
+
? rows.map((row) => {
|
|
1484
|
+
if (row !== existingFolder) return row;
|
|
1485
|
+
const items = Array.isArray(row.items) ? row.items : [];
|
|
1486
|
+
const exists = items.some((entry) => entry?.type === "workflow" && entry?.objectId === item.objectId && entry?.rowId === item.rowId);
|
|
1487
|
+
return exists ? row : { ...row, collapsed: false, items: [...items, item] };
|
|
1488
|
+
})
|
|
1489
|
+
: [
|
|
1490
|
+
...rows,
|
|
1491
|
+
{
|
|
1492
|
+
id: drawerId("folder"),
|
|
1493
|
+
name: folderName,
|
|
1494
|
+
order: rows.length,
|
|
1495
|
+
collapsed: false,
|
|
1496
|
+
icon: "Folder",
|
|
1497
|
+
color: "#f97316",
|
|
1498
|
+
iconBg: "#fff7ed",
|
|
1499
|
+
items: [item]
|
|
1500
|
+
}
|
|
1501
|
+
];
|
|
1502
|
+
return {
|
|
1503
|
+
...dataModel,
|
|
1504
|
+
objects: seededObjects.map((object, index) => index === navIndex ? { ...navObject, rows: nextRows } : object)
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function workflowGraphFromApiRegistry(registryRow) {
|
|
1509
|
+
const graph = buildDefaultOrchestrationGraphFromRegistry(registryRow, {
|
|
1510
|
+
rootPath: creationProfile?.arrayPath || "",
|
|
1511
|
+
});
|
|
1512
|
+
const humanInputNode = {
|
|
1513
|
+
id: "human-input",
|
|
1514
|
+
type: "human-input",
|
|
1515
|
+
label: "Human Input",
|
|
1516
|
+
subtitle: "Manual trigger",
|
|
1517
|
+
config: {
|
|
1518
|
+
action: "form",
|
|
1519
|
+
title: "Run API workflow",
|
|
1520
|
+
instructions: `Trigger ${String(registryRow?.integrationId || "this API").trim()} with optional inputs.`,
|
|
1521
|
+
required: false,
|
|
1522
|
+
requiresInput: false,
|
|
1523
|
+
fields: []
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
return {
|
|
1527
|
+
...graph,
|
|
1528
|
+
nodes: [humanInputNode, ...(Array.isArray(graph.nodes) ? graph.nodes : [])],
|
|
1529
|
+
edges: [
|
|
1530
|
+
{ from: "human-input", to: "input", passes: "manual-input" },
|
|
1531
|
+
...(Array.isArray(graph.edges) ? graph.edges : [])
|
|
1532
|
+
]
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
async function openWorkflowCanvasFromRegistry() {
|
|
1438
1537
|
const integrationId = String(draft?.integrationId || "").trim();
|
|
1439
|
-
if (integrationId
|
|
1440
|
-
|
|
1538
|
+
if (!integrationId) {
|
|
1539
|
+
setDataSourceMessage("This API Registry row needs an integrationId before a workflow can use it.");
|
|
1441
1540
|
return;
|
|
1442
1541
|
}
|
|
1443
|
-
|
|
1542
|
+
setCreatingWorkflowCanvas(true);
|
|
1543
|
+
setDataSourceMessage("");
|
|
1444
1544
|
try {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1545
|
+
const currentRes = await fetch("/api/workspace", { cache: "no-store" });
|
|
1546
|
+
const currentPayload = await currentRes.json();
|
|
1547
|
+
if (!currentRes.ok || !currentPayload.workspaceConfig) {
|
|
1548
|
+
throw new Error(currentPayload.error || "Failed to load workspace");
|
|
1549
|
+
}
|
|
1550
|
+
let next = currentPayload.workspaceConfig;
|
|
1551
|
+
const existingWorkflow = (() => {
|
|
1552
|
+
const objects = Array.isArray(next?.dataModel?.objects) ? next.dataModel.objects : [];
|
|
1553
|
+
for (const object of objects) {
|
|
1554
|
+
if (object?.objectType !== "sandbox-environment") continue;
|
|
1555
|
+
for (const row of Array.isArray(object.rows) ? object.rows : []) {
|
|
1556
|
+
const matches = findSandboxRowsForRegistry({ dataModel: { objects: [object] } }, integrationId).some((candidate) => candidate === row);
|
|
1557
|
+
if (matches) return { objectId: object.id, rowName: row.Name };
|
|
1558
|
+
}
|
|
1457
1559
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
timeoutMs: sandboxToolDraft.timeoutMs,
|
|
1471
|
-
rootPath: sandboxToolDraft.rootPath,
|
|
1472
|
-
instructions: sandboxToolDraft.instructions,
|
|
1473
|
-
agentHost: sandboxToolDraft.agentHost,
|
|
1474
|
-
schedulerRegistryId: sandboxToolDraft.schedulerRegistryId,
|
|
1475
|
-
orchestrationGraph: sandboxToolDraft.orchestrationGraph
|
|
1476
|
-
});
|
|
1477
|
-
next = appendRowsToTable(next, sandboxTable, [newRow]);
|
|
1478
|
-
setCreatedSandboxMeta({
|
|
1479
|
-
objectId: sandboxTable.objectId,
|
|
1480
|
-
name: newRow.Name,
|
|
1481
|
-
authRef: newRow.authRef || sandboxToolDraft.authRef
|
|
1560
|
+
return null;
|
|
1561
|
+
})();
|
|
1562
|
+
if (existingWorkflow?.objectId && existingWorkflow?.rowName) {
|
|
1563
|
+
router.push(`/workflows?object=${encodeURIComponent(existingWorkflow.objectId)}&row=${encodeURIComponent(existingWorkflow.rowName)}&field=orchestrationConfig`);
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
let sandboxTable = listWorkspaceDataModelTables(next).find((t) => t.objectType === "sandbox-environment");
|
|
1568
|
+
if (!sandboxTable) {
|
|
1569
|
+
next = createTypedBusinessObject(next, {
|
|
1570
|
+
name: "Sandbox Environments",
|
|
1571
|
+
objectType: "sandbox-environment"
|
|
1482
1572
|
});
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1573
|
+
sandboxTable = listWorkspaceDataModelTables(next).find((t) => t.objectType === "sandbox-environment");
|
|
1574
|
+
}
|
|
1575
|
+
if (!sandboxTable) throw new Error("No sandbox workflow object exists.");
|
|
1576
|
+
const ensured = ensureSandboxColumns(next, sandboxTable);
|
|
1577
|
+
next = ensured.config;
|
|
1578
|
+
sandboxTable = ensured.sandboxTable;
|
|
1579
|
+
|
|
1580
|
+
const rows = Array.isArray(sandboxTable.rows) ? sandboxTable.rows : [];
|
|
1581
|
+
const baseRowId = slugifyName(`${integrationId}-workflow`) || "api-workflow";
|
|
1582
|
+
const existingNames = new Set(rows.map((row) => String(row?.Name || row?.name || row?.id || "").trim()));
|
|
1583
|
+
let rowId = baseRowId;
|
|
1584
|
+
let suffix = 2;
|
|
1585
|
+
while (existingNames.has(rowId)) {
|
|
1586
|
+
rowId = `${baseRowId}-${suffix}`;
|
|
1587
|
+
suffix += 1;
|
|
1588
|
+
}
|
|
1589
|
+
const nowIso = new Date().toISOString();
|
|
1590
|
+
const draftGraph = workflowGraphFromApiRegistry(draft);
|
|
1591
|
+
const workflowRow = {
|
|
1592
|
+
Name: rowId,
|
|
1593
|
+
runLocality: "local",
|
|
1594
|
+
schedulerRegistryId: "",
|
|
1595
|
+
runtime: "node",
|
|
1596
|
+
adapter: "local-agent-host",
|
|
1597
|
+
agentHost: "claude_local",
|
|
1598
|
+
intelligenceType: "agent-host",
|
|
1599
|
+
localModel: "",
|
|
1600
|
+
localEndpoint: "",
|
|
1601
|
+
intelligenceAdapterMode: "ollama",
|
|
1602
|
+
envRefs: "",
|
|
1603
|
+
networkAllow: "false",
|
|
1604
|
+
allowList: "",
|
|
1605
|
+
instructions: `Draft workflow for ${integrationId}. The canvas starts with a human input trigger and an API Registry call node.`,
|
|
1606
|
+
command: "",
|
|
1607
|
+
orchestrationDraftConfig: serializeOrchestrationGraph(draftGraph),
|
|
1608
|
+
orchestrationDraftStatus: "draft",
|
|
1609
|
+
orchestrationDraftUpdatedAt: nowIso,
|
|
1610
|
+
orchestrationDraftBaseVersion: "0",
|
|
1611
|
+
orchestrationDraftTestPassed: false,
|
|
1612
|
+
orchestrationDraftTestedConfig: "",
|
|
1613
|
+
timeoutMs: "180000",
|
|
1614
|
+
resolverTemplateId: String(draft?.resolverTemplateId || "").trim(),
|
|
1615
|
+
connectorKind: "local-agent-host",
|
|
1616
|
+
executionLane: "workflow",
|
|
1617
|
+
status: "draft",
|
|
1618
|
+
lastTested: "",
|
|
1619
|
+
lastRunId: "",
|
|
1620
|
+
lastSourceId: "",
|
|
1621
|
+
lastResponse: "",
|
|
1622
|
+
description: String(draft?.description || "").trim()
|
|
1623
|
+
};
|
|
1624
|
+
next = appendRowsToTable(next, sandboxTable, [workflowRow]);
|
|
1625
|
+
const finalDataModel = addWorkflowFolderShortcut(next.dataModel, {
|
|
1626
|
+
objectId: sandboxTable.objectId,
|
|
1627
|
+
rowId,
|
|
1628
|
+
label: rowId
|
|
1486
1629
|
});
|
|
1630
|
+
const patchBody = { dataModel: finalDataModel };
|
|
1631
|
+
const preflightResponse = await fetch("/api/workspace/patch/preflight", {
|
|
1632
|
+
method: "POST",
|
|
1633
|
+
headers: { "content-type": "application/json" },
|
|
1634
|
+
body: JSON.stringify(patchBody)
|
|
1635
|
+
});
|
|
1636
|
+
const preflightPayload = await preflightResponse.json();
|
|
1637
|
+
if (!preflightResponse.ok || !preflightPayload.ok) {
|
|
1638
|
+
throw new Error("Workflow creation is blocked by workspace policy.");
|
|
1639
|
+
}
|
|
1640
|
+
const response = await fetch("/api/workspace", {
|
|
1641
|
+
method: "PATCH",
|
|
1642
|
+
headers: { "content-type": "application/json" },
|
|
1643
|
+
body: JSON.stringify(patchBody)
|
|
1644
|
+
});
|
|
1645
|
+
const payload = await response.json();
|
|
1646
|
+
if (!response.ok || !payload.workspaceConfig) {
|
|
1647
|
+
throw new Error("Workflow creation failed. Try again.");
|
|
1648
|
+
}
|
|
1649
|
+
router.push(`/workflows?object=${encodeURIComponent(sandboxTable.objectId)}&row=${encodeURIComponent(rowId)}&field=orchestrationConfig`);
|
|
1650
|
+
} catch (err) {
|
|
1651
|
+
setDataSourceMessage(redactSecretsFromText(err.message || "Failed to open workflow canvas"));
|
|
1487
1652
|
} finally {
|
|
1488
|
-
|
|
1653
|
+
setCreatingWorkflowCanvas(false);
|
|
1489
1654
|
}
|
|
1490
1655
|
}
|
|
1491
1656
|
|
|
@@ -1633,11 +1798,103 @@ function DataModelRecordDrawer({
|
|
|
1633
1798
|
}
|
|
1634
1799
|
}
|
|
1635
1800
|
|
|
1801
|
+
// CMS SDK v1.5.1 — construct the governed resolver from the tested response
|
|
1802
|
+
// shape and stage it for one-screen review. No blank form: rootPath / idField
|
|
1803
|
+
// / entityType / auth header are all derived (or surfaced as `blanks` when the
|
|
1804
|
+
// row is missing a target). Nothing is written until the operator confirms.
|
|
1805
|
+
function stageResolverConstruct() {
|
|
1806
|
+
const integrationId = String(draft?.integrationId || "").trim();
|
|
1807
|
+
if (!integrationId) {
|
|
1808
|
+
setResolverConstructMessage("This API Registry row needs an integrationId before a resolver can be constructed.");
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
const profile = profileApiResponse(draft?.lastResponse);
|
|
1812
|
+
const recommendation = profile ? recommendResolver(profile) : null;
|
|
1813
|
+
const result = constructResolverProposal({
|
|
1814
|
+
row: draft,
|
|
1815
|
+
profile,
|
|
1816
|
+
recommendation,
|
|
1817
|
+
recordRef: { objectId: table?.objectId, rowName: draft?.Name || integrationId },
|
|
1818
|
+
});
|
|
1819
|
+
setResolverConstructMessage("");
|
|
1820
|
+
setResolverConstruct(result);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// Apply the staged resolver through the GOVERNED lane only (helper/apply →
|
|
1824
|
+
// writeResolverProposalFile), mark the row wired (resolverTemplateId), then
|
|
1825
|
+
// re-test through the resolver so the user sees green without leaving the
|
|
1826
|
+
// drawer. config-driven (Nango) and unsupported kinds never reach here.
|
|
1827
|
+
async function applyResolverConstruct() {
|
|
1828
|
+
const staged = resolverConstruct;
|
|
1829
|
+
if (!staged || staged.mode !== "file" || !staged.proposal || !staged.ok) return;
|
|
1830
|
+
const integrationId = String(draft?.integrationId || "").trim();
|
|
1831
|
+
setResolverConstructBusy(true);
|
|
1832
|
+
setResolverConstructMessage("");
|
|
1833
|
+
try {
|
|
1834
|
+
const res = await fetch("/api/workspace/helper/apply", {
|
|
1835
|
+
method: "POST",
|
|
1836
|
+
headers: { "content-type": "application/json" },
|
|
1837
|
+
body: JSON.stringify({ proposals: [staged.proposal], reviewedBy: "cockpit" }),
|
|
1838
|
+
});
|
|
1839
|
+
const payload = await res.json();
|
|
1840
|
+
const appliedOne = Array.isArray(payload.applied)
|
|
1841
|
+
? payload.applied.find((a) => a.type === "resolver.create")
|
|
1842
|
+
: null;
|
|
1843
|
+
if (!res.ok || !appliedOne) {
|
|
1844
|
+
const skip = Array.isArray(payload.skipped) ? payload.skipped[0]?.reason : null;
|
|
1845
|
+
const msg = redactSecretsFromText(skip || payload.error || payload.guidance || "Resolver apply failed");
|
|
1846
|
+
setResolverConstructMessage(msg);
|
|
1847
|
+
pushReceipt({ kind: "resolver-construct", ok: false, detail: msg });
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
// Mark the row wired so the creation journey closes (resolverTemplateId
|
|
1851
|
+
// becomes a real registered resolver id, not the "custom-http" passthrough).
|
|
1852
|
+
onSave((config) => {
|
|
1853
|
+
const t = listWorkspaceDataModelTables(config).find((x) => x.objectId === table?.objectId);
|
|
1854
|
+
if (!t) return config;
|
|
1855
|
+
const idx = (t.rows || []).findIndex((r) => String(r?.integrationId || "").trim() === integrationId);
|
|
1856
|
+
if (idx < 0) return config;
|
|
1857
|
+
return updateTableCell(config, t, idx, "resolverTemplateId", integrationId);
|
|
1858
|
+
});
|
|
1859
|
+
pushReceipt({
|
|
1860
|
+
kind: "resolver-construct",
|
|
1861
|
+
ok: true,
|
|
1862
|
+
detail: `Resolver "${appliedOne.resolverFilename || integrationId}" constructed and wired.`,
|
|
1863
|
+
});
|
|
1864
|
+
// Re-test through the new resolver — the secret stays server-side.
|
|
1865
|
+
try {
|
|
1866
|
+
const testRes = await fetch("/api/workspace/test-source", {
|
|
1867
|
+
method: "POST",
|
|
1868
|
+
headers: { "content-type": "application/json" },
|
|
1869
|
+
body: JSON.stringify({ integrationId, binding: {} }),
|
|
1870
|
+
});
|
|
1871
|
+
const testPayload = await testRes.json();
|
|
1872
|
+
if (testPayload?.ok) {
|
|
1873
|
+
pushReceipt({ kind: "resolver-test", ok: true, detail: `Resolver returned ${testPayload.recordCount ?? 0} record(s).` });
|
|
1874
|
+
} else {
|
|
1875
|
+
pushReceipt({
|
|
1876
|
+
kind: "resolver-test",
|
|
1877
|
+
ok: false,
|
|
1878
|
+
detail: redactSecretsFromText(testPayload?.error || testPayload?.reason || "test-source did not return ok"),
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
} catch {
|
|
1882
|
+
/* non-fatal — the resolver is written; refresh re-tests it later */
|
|
1883
|
+
}
|
|
1884
|
+
setResolverConstruct(null);
|
|
1885
|
+
await reloadCreationSignals();
|
|
1886
|
+
} catch (err) {
|
|
1887
|
+
setResolverConstructMessage(redactSecretsFromText(err.message || "Resolver apply failed"));
|
|
1888
|
+
} finally {
|
|
1889
|
+
setResolverConstructBusy(false);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1636
1893
|
// The creation cockpit emits a single action descriptor per step; map it to
|
|
1637
1894
|
// the drawer's existing governed handlers. No new mutation paths.
|
|
1638
1895
|
async function handleCockpitAction(action) {
|
|
1639
1896
|
if (!action || !action.id) return;
|
|
1640
|
-
const tag = `${action.stepId}:${action.id}`;
|
|
1897
|
+
const tag = `${action.stepId || "workflow"}:${action.id}`;
|
|
1641
1898
|
setCockpitBusy(tag);
|
|
1642
1899
|
try {
|
|
1643
1900
|
switch (action.id) {
|
|
@@ -1649,6 +1906,11 @@ function DataModelRecordDrawer({
|
|
|
1649
1906
|
onClose();
|
|
1650
1907
|
router.push(action.href || "/settings");
|
|
1651
1908
|
break;
|
|
1909
|
+
case "construct-resolver":
|
|
1910
|
+
// CMS SDK v1.5.1 — construct the governed resolver from the tested
|
|
1911
|
+
// response shape (no blank form) and stage it for one-screen review.
|
|
1912
|
+
stageResolverConstruct();
|
|
1913
|
+
break;
|
|
1652
1914
|
case "open-resolver":
|
|
1653
1915
|
// Hand off to the governed helper widget — the resolver proposal lane.
|
|
1654
1916
|
// Carries the integrationId so the helper can scope a resolver proposal.
|
|
@@ -1665,8 +1927,9 @@ function DataModelRecordDrawer({
|
|
|
1665
1927
|
case "open-data-source":
|
|
1666
1928
|
openDataSourceRow(action.objectId);
|
|
1667
1929
|
break;
|
|
1668
|
-
case "create-
|
|
1669
|
-
|
|
1930
|
+
case "create-workflow-canvas":
|
|
1931
|
+
case "open-workflow-canvas":
|
|
1932
|
+
await openWorkflowCanvasFromRegistry();
|
|
1670
1933
|
break;
|
|
1671
1934
|
case "refresh-source":
|
|
1672
1935
|
await refreshLinkedSource({ objectId: action.objectId });
|
|
@@ -1704,78 +1967,6 @@ function DataModelRecordDrawer({
|
|
|
1704
1967
|
}
|
|
1705
1968
|
: null;
|
|
1706
1969
|
|
|
1707
|
-
async function runSandboxToolByName({ objectId, name }) {
|
|
1708
|
-
const rowName = String(name || "").trim();
|
|
1709
|
-
const objectIdValue = String(objectId || "").trim();
|
|
1710
|
-
if (!rowName || !objectIdValue) return;
|
|
1711
|
-
setCreatedSandboxTesting(true);
|
|
1712
|
-
setCreatedSandboxTestMessage("");
|
|
1713
|
-
try {
|
|
1714
|
-
const res = await fetch("/api/workspace/sandbox-run", {
|
|
1715
|
-
method: "POST",
|
|
1716
|
-
headers: { "content-type": "application/json" },
|
|
1717
|
-
body: JSON.stringify({ objectId: objectIdValue, name: rowName }),
|
|
1718
|
-
});
|
|
1719
|
-
const payload = await res.json();
|
|
1720
|
-
const responseText = redactSecretsFromText(JSON.stringify(payload.response ?? payload, null, 2));
|
|
1721
|
-
const status = payload.ok && String(payload.status || "").toLowerCase() === "connected" ? "connected" : "failed";
|
|
1722
|
-
const testedAt = payload.response?.ranAt || new Date().toISOString();
|
|
1723
|
-
const lastRunId = payload.runId || payload.response?.runId || "";
|
|
1724
|
-
const lastSourceId = payload.sourceId || payload.response?.sourceId || "";
|
|
1725
|
-
onSave((config) => {
|
|
1726
|
-
const sandboxTable = listWorkspaceDataModelTables(config).find((t) => t.objectType === "sandbox-environment");
|
|
1727
|
-
if (!sandboxTable) return config;
|
|
1728
|
-
const idx = (sandboxTable.rows || []).findIndex((r) => String(r?.Name || "").trim() === rowName);
|
|
1729
|
-
if (idx < 0) return config;
|
|
1730
|
-
let next = updateTableCell(config, sandboxTable, idx, "status", status);
|
|
1731
|
-
next = updateTableCell(next, sandboxTable, idx, "lastTested", testedAt);
|
|
1732
|
-
next = updateTableCell(next, sandboxTable, idx, "lastRunId", lastRunId);
|
|
1733
|
-
next = updateTableCell(next, sandboxTable, idx, "lastSourceId", lastSourceId);
|
|
1734
|
-
next = updateTableCell(next, sandboxTable, idx, "lastResponse", responseText);
|
|
1735
|
-
return next;
|
|
1736
|
-
});
|
|
1737
|
-
const safeError = redactSecretsFromText(
|
|
1738
|
-
payload.response?.error || payload.error || "Sandbox run failed"
|
|
1739
|
-
);
|
|
1740
|
-
setCreatedSandboxTestMessage(
|
|
1741
|
-
status === "connected"
|
|
1742
|
-
? "Sandbox run succeeded — lastResponse and source record saved."
|
|
1743
|
-
: safeError
|
|
1744
|
-
);
|
|
1745
|
-
} catch (err) {
|
|
1746
|
-
setCreatedSandboxTestMessage(redactSecretsFromText(err.message || "Sandbox run failed"));
|
|
1747
|
-
} finally {
|
|
1748
|
-
setCreatedSandboxTesting(false);
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
function resolveSandboxTableMeta() {
|
|
1753
|
-
const sandboxTable = tables.find((t) => t.objectType === "sandbox-environment")
|
|
1754
|
-
|| (workspaceConfig ? listWorkspaceDataModelTables(workspaceConfig).find((t) => t.objectType === "sandbox-environment") : null);
|
|
1755
|
-
return sandboxTable?.objectId ? { objectId: sandboxTable.objectId, table: sandboxTable } : null;
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
async function runCreatedSandboxTest() {
|
|
1759
|
-
if (!createdSandboxMeta?.objectId || !createdSandboxMeta?.name) return;
|
|
1760
|
-
await runSandboxToolByName(createdSandboxMeta);
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
async function runExistingSandboxTool({ name }) {
|
|
1764
|
-
const meta = resolveSandboxTableMeta();
|
|
1765
|
-
if (!meta) {
|
|
1766
|
-
setCreatedSandboxTestMessage("No sandbox-environment table in this workspace.");
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
await runSandboxToolByName({ objectId: meta.objectId, name });
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
function openSandboxToolRow({ name }) {
|
|
1773
|
-
const rowName = String(name || "").trim();
|
|
1774
|
-
if (!rowName) return;
|
|
1775
|
-
onFocusSandboxRow?.({ rowName });
|
|
1776
|
-
onClose();
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
1970
|
async function runSandbox() {
|
|
1780
1971
|
if (!table.objectId) {
|
|
1781
1972
|
setSandboxMessage("Missing object id for this sandbox table.");
|
|
@@ -1874,7 +2065,7 @@ function DataModelRecordDrawer({
|
|
|
1874
2065
|
onClearInitialSidecar?.();
|
|
1875
2066
|
}
|
|
1876
2067
|
|
|
1877
|
-
const drawerWide =
|
|
2068
|
+
const drawerWide = sidecarMode === "trace";
|
|
1878
2069
|
const hideRecordFields = isSandbox && sidecarMode === "trace";
|
|
1879
2070
|
|
|
1880
2071
|
return (
|
|
@@ -1903,7 +2094,7 @@ function DataModelRecordDrawer({
|
|
|
1903
2094
|
{sandboxRunning ? "Running…" : "Execute"}
|
|
1904
2095
|
</button>
|
|
1905
2096
|
)}
|
|
1906
|
-
{!isSandbox &&
|
|
2097
|
+
{!isSandbox && (
|
|
1907
2098
|
<button type="button" className="dm-sidebar-close" onClick={() => setEditMode((current) => !current)} aria-label="Toggle edit mode">
|
|
1908
2099
|
<Pencil size={16} />
|
|
1909
2100
|
</button>
|
|
@@ -1913,7 +2104,7 @@ function DataModelRecordDrawer({
|
|
|
1913
2104
|
</button>
|
|
1914
2105
|
</div>
|
|
1915
2106
|
</header>
|
|
1916
|
-
{table.objectType === "data-source" &&
|
|
2107
|
+
{table.objectType === "data-source" && (
|
|
1917
2108
|
<SourceTestPanel
|
|
1918
2109
|
status={draft.status}
|
|
1919
2110
|
testing={testing}
|
|
@@ -1923,41 +2114,13 @@ function DataModelRecordDrawer({
|
|
|
1923
2114
|
/>
|
|
1924
2115
|
)}
|
|
1925
2116
|
<div className="dm-record-scroll" ref={drawerScrollRef}>
|
|
1926
|
-
{isApiRegistry &&
|
|
1927
|
-
<section className="dm-api-action-card dm-api-action-card-success" aria-label="Sandbox tool created">
|
|
1928
|
-
<div className="dm-api-action-card-body">
|
|
1929
|
-
<p className="dm-api-action-card-eyebrow">Sandbox tool created</p>
|
|
1930
|
-
<h3>{createdSandboxMeta.name}</h3>
|
|
1931
|
-
<p>Governed sandbox row saved with orchestrationGraph. Run test to persist lastResponse — nothing auto-runs.</p>
|
|
1932
|
-
{createdSandboxTestMessage && <p className="dm-sandbox-tool-test-msg">{createdSandboxTestMessage}</p>}
|
|
1933
|
-
</div>
|
|
1934
|
-
<div className="dm-api-action-card-actions">
|
|
1935
|
-
<button
|
|
1936
|
-
type="button"
|
|
1937
|
-
className="dm-btn-outline dm-api-action-card-cta"
|
|
1938
|
-
disabled={saving}
|
|
1939
|
-
onClick={() => openSandboxToolRow({ name: createdSandboxMeta.name })}
|
|
1940
|
-
>
|
|
1941
|
-
Open sandbox tool
|
|
1942
|
-
</button>
|
|
1943
|
-
<button
|
|
1944
|
-
type="button"
|
|
1945
|
-
className="dm-btn-primary-sm dm-api-action-card-cta"
|
|
1946
|
-
disabled={createdSandboxTesting || saving}
|
|
1947
|
-
onClick={runCreatedSandboxTest}
|
|
1948
|
-
>
|
|
1949
|
-
{createdSandboxTesting ? "Running…" : "Run sandbox"}
|
|
1950
|
-
</button>
|
|
1951
|
-
</div>
|
|
1952
|
-
</section>
|
|
1953
|
-
)}
|
|
1954
|
-
{isApiRegistry && sandboxToolFlow !== "draft" && sandboxToolFlow !== "confirm" && creationState && (
|
|
2117
|
+
{isApiRegistry && creationState && (
|
|
1955
2118
|
<>
|
|
1956
2119
|
<ApiRegistryCreationCockpit
|
|
1957
2120
|
state={creationState}
|
|
1958
2121
|
onAction={handleCockpitAction}
|
|
1959
2122
|
busyAction={cockpitBusy}
|
|
1960
|
-
disabled={saving || creatingDataSource || testing}
|
|
2123
|
+
disabled={saving || creatingDataSource || testing || creatingWorkflowCanvas}
|
|
1961
2124
|
profile={creationProfile}
|
|
1962
2125
|
resolverRec={creationResolverRec}
|
|
1963
2126
|
receipts={creationReceipts}
|
|
@@ -1966,28 +2129,77 @@ function DataModelRecordDrawer({
|
|
|
1966
2129
|
hideWhenComplete
|
|
1967
2130
|
onCollapsedChange={setCockpitCollapsed}
|
|
1968
2131
|
/>
|
|
2132
|
+
{resolverConstruct ? (
|
|
2133
|
+
<section className="dm-api-action-card dm-cockpit" aria-label="Construct resolver review">
|
|
2134
|
+
<div className="dm-cockpit-shape">
|
|
2135
|
+
<div className="dm-cockpit-shape-head">
|
|
2136
|
+
<p className="dm-api-action-card-eyebrow">Make this API usable · {resolverConstruct.connectorKind}</p>
|
|
2137
|
+
{resolverConstruct.detected?.confidence ? (
|
|
2138
|
+
<span className={`dm-db-status ${resolverConstruct.detected.confidence === "high" ? "ok" : resolverConstruct.detected.confidence === "low" ? "bad" : "warn"}`}>
|
|
2139
|
+
<span />
|
|
2140
|
+
{resolverConstruct.detected.confidence} confidence
|
|
2141
|
+
</span>
|
|
2142
|
+
) : null}
|
|
2143
|
+
</div>
|
|
2144
|
+
{resolverConstruct.detected?.sentence ? (
|
|
2145
|
+
<p className="dm-cockpit-step-desc"><b>{resolverConstruct.detected.sentence}</b></p>
|
|
2146
|
+
) : null}
|
|
2147
|
+
<p className="dm-cockpit-step-desc">{resolverConstruct.reason}</p>
|
|
2148
|
+
{resolverConstruct.mode === "file" && resolverConstruct.authRef ? (
|
|
2149
|
+
<p className="dm-cockpit-step-hint">Safe: the secret stays server-side via <code>{resolverConstruct.authRef}</code> — it never reaches the browser or the generated file.</p>
|
|
2150
|
+
) : null}
|
|
2151
|
+
{resolverConstruct.mode === "file" && resolverConstruct.prefill ? (
|
|
2152
|
+
<div className="dm-cockpit-fields">
|
|
2153
|
+
<span className="dm-cockpit-field"><b>records at</b>{resolverConstruct.prefill.rootPath || "top-level"}</span>
|
|
2154
|
+
<span className="dm-cockpit-field"><b>row id</b>{resolverConstruct.prefill.idField}</span>
|
|
2155
|
+
<span className="dm-cockpit-field"><b>entity</b>{resolverConstruct.prefill.entityType}</span>
|
|
2156
|
+
{resolverConstruct.proposal?.target?.path ? (
|
|
2157
|
+
<span className="dm-cockpit-field"><b>file</b>{resolverConstruct.proposal.target.path}</span>
|
|
2158
|
+
) : null}
|
|
2159
|
+
{resolverConstruct.endpoint ? (
|
|
2160
|
+
<span className="dm-cockpit-field"><b>endpoint</b>{resolverConstruct.endpoint}</span>
|
|
2161
|
+
) : null}
|
|
2162
|
+
</div>
|
|
2163
|
+
) : null}
|
|
2164
|
+
{resolverConstruct.mode === "config-driven" && resolverConstruct.endpoint ? (
|
|
2165
|
+
<div className="dm-cockpit-fields">
|
|
2166
|
+
<span className="dm-cockpit-field"><b>type</b>config-driven (no file)</span>
|
|
2167
|
+
<span className="dm-cockpit-field"><b>endpoint</b>{resolverConstruct.endpoint}</span>
|
|
2168
|
+
</div>
|
|
2169
|
+
) : null}
|
|
2170
|
+
{resolverConstruct.blanks?.length ? (
|
|
2171
|
+
<p className="dm-cockpit-step-hint">Missing on the row: {resolverConstruct.blanks.join(", ")}</p>
|
|
2172
|
+
) : null}
|
|
2173
|
+
{resolverConstruct.mode === "file" && resolverConstruct.ok ? (
|
|
2174
|
+
<p className="dm-cockpit-step-hint">After apply: the resolver file is written via the governed lane, the row is marked wired, and it's re-tested automatically. If the re-test fails, the resolver is still written — the receipt distinguishes “written” from “runtime failed”.</p>
|
|
2175
|
+
) : null}
|
|
2176
|
+
{resolverConstructMessage ? <p className="dm-cockpit-step-hint">{resolverConstructMessage}</p> : null}
|
|
2177
|
+
<div className="dm-cockpit-shape-head">
|
|
2178
|
+
{resolverConstruct.mode === "file" && resolverConstruct.ok ? (
|
|
2179
|
+
<button
|
|
2180
|
+
type="button"
|
|
2181
|
+
className="dm-btn-primary-sm"
|
|
2182
|
+
disabled={resolverConstructBusy}
|
|
2183
|
+
onClick={applyResolverConstruct}
|
|
2184
|
+
>
|
|
2185
|
+
{resolverConstructBusy ? "Applying…" : "Apply resolver"}
|
|
2186
|
+
</button>
|
|
2187
|
+
) : null}
|
|
2188
|
+
<button
|
|
2189
|
+
type="button"
|
|
2190
|
+
className="dm-btn-outline"
|
|
2191
|
+
disabled={resolverConstructBusy}
|
|
2192
|
+
onClick={() => { setResolverConstruct(null); setResolverConstructMessage(""); }}
|
|
2193
|
+
>
|
|
2194
|
+
{resolverConstruct.mode === "file" && resolverConstruct.ok ? "Cancel" : "Dismiss"}
|
|
2195
|
+
</button>
|
|
2196
|
+
</div>
|
|
2197
|
+
</div>
|
|
2198
|
+
</section>
|
|
2199
|
+
) : null}
|
|
1969
2200
|
{dataSourceMessage ? <p className="dm-sandbox-tool-test-msg">{dataSourceMessage}</p> : null}
|
|
1970
2201
|
</>
|
|
1971
2202
|
)}
|
|
1972
|
-
{isApiRegistry && sandboxToolFlow === "draft" && (
|
|
1973
|
-
<SandboxToolDraftPanel
|
|
1974
|
-
registryRow={draft}
|
|
1975
|
-
draftOptions={sandboxToolDraft}
|
|
1976
|
-
disabled={saving || sandboxToolCreating}
|
|
1977
|
-
onDraftChange={setSandboxToolDraft}
|
|
1978
|
-
onRequestConfirm={() => setSandboxToolFlow("confirm")}
|
|
1979
|
-
onCancel={() => setSandboxToolFlow(null)}
|
|
1980
|
-
/>
|
|
1981
|
-
)}
|
|
1982
|
-
<SandboxToolConfirmModal
|
|
1983
|
-
open={isApiRegistry && sandboxToolFlow === "confirm"}
|
|
1984
|
-
toolName={sandboxToolDraft?.name || ""}
|
|
1985
|
-
authRef={sandboxToolDraft?.authRef || draft.authRef}
|
|
1986
|
-
orchestrationGraph={sandboxToolDraft?.orchestrationGraph}
|
|
1987
|
-
creating={sandboxToolCreating}
|
|
1988
|
-
onConfirm={createSandboxToolFromRegistry}
|
|
1989
|
-
onCancel={() => setSandboxToolFlow("draft")}
|
|
1990
|
-
/>
|
|
1991
2203
|
{isSandbox && sidecarMode !== "graph" && sidecarMode !== "trace" && sandboxMessage && (
|
|
1992
2204
|
<SandboxRunPanel
|
|
1993
2205
|
status={draft.status}
|
|
@@ -2024,7 +2236,7 @@ function DataModelRecordDrawer({
|
|
|
2024
2236
|
/>
|
|
2025
2237
|
)}
|
|
2026
2238
|
<div className="dm-record-fields">
|
|
2027
|
-
{
|
|
2239
|
+
{hideRecordFields ? null : isSandbox ? (
|
|
2028
2240
|
<SandboxRecordFields
|
|
2029
2241
|
draft={draft}
|
|
2030
2242
|
setDraft={setDraft}
|