@growthub/cli 0.14.1 → 0.14.3
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/SKILL.md +4 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/agent-outcomes/route.js +85 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/apps/route.js +187 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +36 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/patch/preflight/route.js +152 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +21 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +88 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +3 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +86 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/swarm-condition/route.js +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +21 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/workflow/publish/route.js +338 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceLensPanel.jsx +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +49 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +54 -11
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +113 -36
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +34 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +7 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +35 -169
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +26 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/adapters/local-intelligence-browser-access.js +516 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-agent-host.js +85 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-process.js +3 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/index.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/sandbox-adapter-registry.js +5 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/data-model/field-contracts.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +8 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +3 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +4 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-publish.js +179 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +82 -27
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +4 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +89 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-app-registry.js +539 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +11 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +24 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-outcome-receipts.js +157 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-policy.js +400 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +6 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-swarm-proposal.js +3 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +364 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +10 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/skills/governed-workspace-mutation/SKILL.md +203 -0
- package/package.json +2 -2
|
@@ -44,6 +44,11 @@ import {
|
|
|
44
44
|
validateOrchestrationGraph
|
|
45
45
|
} from "@/lib/orchestration-graph";
|
|
46
46
|
import { resolveConnectorAction } from "@/lib/orchestration-sidecar-routing";
|
|
47
|
+
import {
|
|
48
|
+
nodeSandboxRecordRef,
|
|
49
|
+
patchSandboxRowInConfig,
|
|
50
|
+
withGraphSandboxRecordRefs
|
|
51
|
+
} from "@/lib/orchestration-publish";
|
|
47
52
|
import { OrchestrationGraphCanvas } from "../data-model/components/OrchestrationGraphCanvas.jsx";
|
|
48
53
|
import { OrchestrationGraphEmptyCanvas } from "../data-model/components/OrchestrationGraphEmptyCanvas.jsx";
|
|
49
54
|
import { OrchestrationNodeConfigPanel } from "../data-model/components/OrchestrationNodeConfigPanel.jsx";
|
|
@@ -117,47 +122,6 @@ function resolveRegistryRefForSandbox(workspaceConfig, sandboxRow) {
|
|
|
117
122
|
return firstRegistryRow ? { object: firstRegistryObject, row: firstRegistryRow } : null;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
function patchSandboxRowInConfig(workspaceConfig, objectId, rowIndex, fields) {
|
|
121
|
-
const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
|
|
122
|
-
return {
|
|
123
|
-
...workspaceConfig,
|
|
124
|
-
dataModel: {
|
|
125
|
-
...workspaceConfig.dataModel,
|
|
126
|
-
objects: objects.map((object) => {
|
|
127
|
-
if (object?.id !== objectId) return object;
|
|
128
|
-
const rows = Array.isArray(object.rows) ? object.rows : [];
|
|
129
|
-
return {
|
|
130
|
-
...object,
|
|
131
|
-
rows: rows.map((row, index) => (index === rowIndex ? { ...row, ...fields } : row)),
|
|
132
|
-
};
|
|
133
|
-
}),
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function nodeSandboxRecordRef(objectId, rowName, nodeId) {
|
|
139
|
-
return {
|
|
140
|
-
objectId: String(objectId || "").trim(),
|
|
141
|
-
rowName: String(rowName || "").trim(),
|
|
142
|
-
nodeId: String(nodeId || "").trim()
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function withGraphSandboxRecordRefs(graph, objectId, rowName) {
|
|
147
|
-
const parsed = parseOrchestrationGraph(graph) || graph;
|
|
148
|
-
if (!parsed || typeof parsed !== "object") return parsed;
|
|
149
|
-
return {
|
|
150
|
-
...parsed,
|
|
151
|
-
nodes: (Array.isArray(parsed.nodes) ? parsed.nodes : []).map((node) => ({
|
|
152
|
-
...node,
|
|
153
|
-
config: {
|
|
154
|
-
...(node?.config || {}),
|
|
155
|
-
sandboxRecordRef: nodeSandboxRecordRef(objectId, rowName, node?.id)
|
|
156
|
-
}
|
|
157
|
-
}))
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
125
|
const WORKFLOW_ACTION_GROUPS = [
|
|
162
126
|
{
|
|
163
127
|
label: "Data",
|
|
@@ -201,90 +165,6 @@ function getWorkspaceObjectOptions(workspaceConfig) {
|
|
|
201
165
|
}));
|
|
202
166
|
}
|
|
203
167
|
|
|
204
|
-
function normalizeDeltaTags(tags) {
|
|
205
|
-
return Array.from(new Set((Array.isArray(tags) ? tags : [])
|
|
206
|
-
.map((tag) => String(tag || "").trim().toLowerCase())
|
|
207
|
-
.filter(Boolean)));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function inferDeltaTagsForWorkflowNode(node, config) {
|
|
211
|
-
const tags = [];
|
|
212
|
-
const type = String(node?.type || "").trim();
|
|
213
|
-
const action = String(config?.action || node?.id || "").trim();
|
|
214
|
-
if (type === "thinAdapter") tags.push("model", "prompt", "routing");
|
|
215
|
-
if (type === "ai-agent") tags.push("model", "prompt", "output");
|
|
216
|
-
if (type === "data-action" || type === "data-trigger") tags.push("input", "output");
|
|
217
|
-
if (type === "flow-control") tags.push("routing");
|
|
218
|
-
if (type === "core-action") tags.push("runtime");
|
|
219
|
-
if (type === "human-input") tags.push("input");
|
|
220
|
-
if (action.includes("search") || action.includes("filter")) tags.push("evaluation", "guardrail");
|
|
221
|
-
if (action.includes("delete") || config?.confirmationRequired) tags.push("guardrail");
|
|
222
|
-
if (action.includes("http") || config?.url || config?.method) tags.push("routing", "input", "output");
|
|
223
|
-
if (action.includes("email")) tags.push("input", "output");
|
|
224
|
-
if (action.includes("delay") || config?.duration || config?.unit) tags.push("runtime");
|
|
225
|
-
if (config?.objectId || config?.fieldMap || config?.filters) tags.push("input", "output");
|
|
226
|
-
if (config?.model || config?.prompt) tags.push("model", "prompt");
|
|
227
|
-
return normalizeDeltaTags(tags);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function getNodeDeltaRecords(previousGraph, nextGraph) {
|
|
231
|
-
const previousNodes = new Map(
|
|
232
|
-
(Array.isArray(previousGraph?.nodes) ? previousGraph.nodes : [])
|
|
233
|
-
.map((node) => [String(node?.id || ""), node])
|
|
234
|
-
.filter(([id]) => id)
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
return (Array.isArray(nextGraph?.nodes) ? nextGraph.nodes : [])
|
|
238
|
-
.map((node) => {
|
|
239
|
-
const nodeId = String(node?.id || "").trim();
|
|
240
|
-
if (!nodeId) return null;
|
|
241
|
-
const previous = previousNodes.get(nodeId);
|
|
242
|
-
const config = node?.config && typeof node.config === "object" && !Array.isArray(node.config) ? node.config : {};
|
|
243
|
-
const previousConfig = previous?.config && typeof previous.config === "object" && !Array.isArray(previous.config)
|
|
244
|
-
? previous.config
|
|
245
|
-
: {};
|
|
246
|
-
const currentComparable = JSON.stringify({
|
|
247
|
-
type: node?.type || "",
|
|
248
|
-
sandbox: node?.sandbox || "",
|
|
249
|
-
label: node?.label || "",
|
|
250
|
-
subtitle: node?.subtitle || "",
|
|
251
|
-
config
|
|
252
|
-
});
|
|
253
|
-
const previousComparable = JSON.stringify({
|
|
254
|
-
type: previous?.type || "",
|
|
255
|
-
sandbox: previous?.sandbox || "",
|
|
256
|
-
label: previous?.label || "",
|
|
257
|
-
subtitle: previous?.subtitle || "",
|
|
258
|
-
config: previousConfig
|
|
259
|
-
});
|
|
260
|
-
const explicitTags = normalizeDeltaTags(config.deltaTags);
|
|
261
|
-
const deltaTags = explicitTags.length > 0 ? explicitTags : inferDeltaTagsForWorkflowNode(node, config);
|
|
262
|
-
const changeReason = String(config.changeReason || "").trim();
|
|
263
|
-
const changed = currentComparable !== previousComparable;
|
|
264
|
-
if (!changed && !changeReason && deltaTags.length === 0) return null;
|
|
265
|
-
return {
|
|
266
|
-
nodeId,
|
|
267
|
-
nodeType: String(node?.type || ""),
|
|
268
|
-
label: String(node?.label || node?.sandbox || nodeId),
|
|
269
|
-
sandboxRecordRef: config.sandboxRecordRef || null,
|
|
270
|
-
changeReason,
|
|
271
|
-
deltaTags,
|
|
272
|
-
requiresRetest: config.requiresRetest !== false,
|
|
273
|
-
previous: previous ? {
|
|
274
|
-
type: String(previous.type || ""),
|
|
275
|
-
sandbox: String(previous.sandbox || ""),
|
|
276
|
-
label: String(previous.label || "")
|
|
277
|
-
} : null,
|
|
278
|
-
next: {
|
|
279
|
-
type: String(node.type || ""),
|
|
280
|
-
sandbox: String(node.sandbox || ""),
|
|
281
|
-
label: String(node.label || "")
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
})
|
|
285
|
-
.filter(Boolean);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
168
|
function makeWorkflowNode(action, workspaceConfig, graph) {
|
|
289
169
|
const baseId = String(action.id || action.type || "step").replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
290
170
|
const existingIds = new Set((Array.isArray(graph?.nodes) ? graph.nodes : []).map((node) => String(node.id)));
|
|
@@ -606,54 +486,31 @@ export default function WorkflowSurface() {
|
|
|
606
486
|
|
|
607
487
|
async function publishGraph() {
|
|
608
488
|
if (resolved.rowIndex < 0 || !objectId) return;
|
|
489
|
+
// Publish is server-authoritative: POST /api/workspace/workflow/publish
|
|
490
|
+
// verifies the saved draft + passing test against the persisted row and
|
|
491
|
+
// owns the version bump, delta record, and draft → live promotion.
|
|
492
|
+
// Direct PATCH of live workflow fields is rejected by the runtime policy.
|
|
609
493
|
const serialized = serializeCurrentGraph();
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
setSaveMessage("Publish blocked. Save and test this exact draft successfully before publishing.");
|
|
494
|
+
const savedDraft = String(sandboxRow?.[draftFieldName] || "");
|
|
495
|
+
if (dirty || serialized !== savedDraft) {
|
|
496
|
+
setSaveMessage("Publish blocked. Save this draft first — publish promotes the saved, tested draft.");
|
|
614
497
|
return;
|
|
615
498
|
}
|
|
616
499
|
setPublishing(true);
|
|
617
500
|
setSaveMessage("");
|
|
618
501
|
try {
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const graphWithRefs = withGraphSandboxRecordRefs(orchestrationGraph, objectId, rowId);
|
|
624
|
-
const nodeDeltas = getNodeDeltaRecords(previousPublishedGraph, graphWithRefs);
|
|
625
|
-
const deltaTags = normalizeDeltaTags(nodeDeltas.flatMap((delta) => delta.deltaTags));
|
|
626
|
-
const changeReason = nodeDeltas.map((delta) => delta.changeReason).filter(Boolean).join("\n");
|
|
627
|
-
const next = patchSandboxRowInConfig(workspaceConfig, objectId, resolved.rowIndex, {
|
|
628
|
-
[effectiveFieldName]: serialized,
|
|
629
|
-
[draftFieldName]: "",
|
|
630
|
-
version: nextVersion,
|
|
631
|
-
lifecycleStatus: "live",
|
|
632
|
-
orchestrationDraftStatus: "published",
|
|
633
|
-
orchestrationDraftTestPassed: false,
|
|
634
|
-
orchestrationDraftTestedConfig: "",
|
|
635
|
-
orchestrationPublishedAt: new Date().toISOString(),
|
|
636
|
-
orchestrationDeltas: [
|
|
637
|
-
...previousDeltas,
|
|
638
|
-
{
|
|
639
|
-
at: new Date().toISOString(),
|
|
640
|
-
version: nextVersion,
|
|
641
|
-
field: effectiveFieldName,
|
|
642
|
-
action: "publish",
|
|
643
|
-
previousVersion: String(sandboxRow?.version || "1"),
|
|
644
|
-
draftTestedAt: sandboxRow?.orchestrationDraftLastTested || "",
|
|
645
|
-
draftRunId: sandboxRow?.orchestrationDraftLastRunId || "",
|
|
646
|
-
changeReason,
|
|
647
|
-
deltaTags,
|
|
648
|
-
nodeDeltas,
|
|
649
|
-
nodeCount: Array.isArray(orchestrationGraph?.nodes) ? orchestrationGraph.nodes.length : 0,
|
|
650
|
-
edgeCount: Array.isArray(orchestrationGraph?.edges) ? orchestrationGraph.edges.length : 0
|
|
651
|
-
}
|
|
652
|
-
]
|
|
502
|
+
const res = await fetch("/api/workspace/workflow/publish", {
|
|
503
|
+
method: "POST",
|
|
504
|
+
headers: { "content-type": "application/json" },
|
|
505
|
+
body: JSON.stringify({ objectId, name: rowId, field: effectiveFieldName }),
|
|
653
506
|
});
|
|
654
|
-
await
|
|
507
|
+
const payload = await res.json();
|
|
508
|
+
if (!res.ok || !payload.ok) {
|
|
509
|
+
throw new Error(payload.error || "Publish failed");
|
|
510
|
+
}
|
|
511
|
+
setWorkspaceConfig(payload.workspaceConfig || workspaceConfig);
|
|
655
512
|
setDirty(false);
|
|
656
|
-
setSaveMessage(`Published orchestration config v${
|
|
513
|
+
setSaveMessage(`Published orchestration config v${payload.version}.`);
|
|
657
514
|
} catch (err) {
|
|
658
515
|
setSaveMessage(err.message || "Publish failed");
|
|
659
516
|
} finally {
|
|
@@ -912,6 +769,7 @@ export default function WorkflowSurface() {
|
|
|
912
769
|
})
|
|
913
770
|
: null;
|
|
914
771
|
const isServerlessWorkflow = Boolean(serverlessState?.isServerless);
|
|
772
|
+
const showServerlessUpgrade = String(sandboxRow?.adapter || "").trim() !== "local-intelligence";
|
|
915
773
|
|
|
916
774
|
async function patchSandboxAndPersist(fields) {
|
|
917
775
|
if (resolved.rowIndex < 0 || !objectId || !workspaceConfig) return;
|
|
@@ -1013,7 +871,7 @@ export default function WorkflowSurface() {
|
|
|
1013
871
|
>
|
|
1014
872
|
<ArrowUp size={13} />
|
|
1015
873
|
</button>
|
|
1016
|
-
{sandboxRow && (
|
|
874
|
+
{sandboxRow && showServerlessUpgrade && (
|
|
1017
875
|
<button
|
|
1018
876
|
type="button"
|
|
1019
877
|
className={"dm-workflow-icon-btn dm-workflow-upgrade-btn" + (isServerlessWorkflow ? " is-serverless" : (upgradeState.showOnboarding ? " is-pulse" : ""))}
|
|
@@ -1051,7 +909,13 @@ export default function WorkflowSurface() {
|
|
|
1051
909
|
<Power size={13} /> {publishing ? "Publishing" : "Publish"}
|
|
1052
910
|
</button>
|
|
1053
911
|
)}
|
|
1054
|
-
<button
|
|
912
|
+
<button
|
|
913
|
+
type="button"
|
|
914
|
+
className="dm-workflow-chip-btn"
|
|
915
|
+
onClick={() => {
|
|
916
|
+
if (sandboxRow) openTraceMode();
|
|
917
|
+
}}
|
|
918
|
+
>
|
|
1055
919
|
<History size={13} /> See Runs
|
|
1056
920
|
</button>
|
|
1057
921
|
{sidecarMode === "trace" && (
|
|
@@ -1093,7 +957,7 @@ export default function WorkflowSurface() {
|
|
|
1093
957
|
|
|
1094
958
|
{/* One-time serverless upgrade onboarding — shows only when the operator
|
|
1095
959
|
has workflows but none are serverless, and hasn't dismissed it. */}
|
|
1096
|
-
{sandboxRow && !upgradeOpen && upgradeState.showOnboarding ? (
|
|
960
|
+
{sandboxRow && showServerlessUpgrade && !upgradeOpen && upgradeState.showOnboarding ? (
|
|
1097
961
|
<div className="workspace-template-context-banner dm-workflow-upgrade-nudge" role="note">
|
|
1098
962
|
<div>
|
|
1099
963
|
<strong>{upgradeState.headline}</strong>
|
|
@@ -1111,7 +975,7 @@ export default function WorkflowSurface() {
|
|
|
1111
975
|
{/* Serverless cockpit — same derivation + cockpit interface as the API
|
|
1112
976
|
Registry and sandbox lanes. Toggles patch the sandbox row; deep config
|
|
1113
977
|
(scheduler/adapter) routes to the object's Data Model drawer. */}
|
|
1114
|
-
{sandboxRow && upgradeOpen && serverlessState ? (
|
|
978
|
+
{sandboxRow && showServerlessUpgrade && upgradeOpen && serverlessState ? (
|
|
1115
979
|
<div className="dm-workflow-upgrade-panel">
|
|
1116
980
|
<div className="dm-workflow-upgrade-panel-head">
|
|
1117
981
|
<span className="dm-api-action-card-eyebrow">Persistence & scheduling</span>
|
|
@@ -1233,6 +1097,8 @@ export default function WorkflowSurface() {
|
|
|
1233
1097
|
graph={orchestrationGraph}
|
|
1234
1098
|
objectId={objectId}
|
|
1235
1099
|
rowName={rowId}
|
|
1100
|
+
sandboxRow={sandboxRow}
|
|
1101
|
+
onSandboxRowPatch={patchSandboxRuntimeFields}
|
|
1236
1102
|
disabled={false}
|
|
1237
1103
|
onGraphChange={(updater) => {
|
|
1238
1104
|
setOrchestrationGraph((g) => (typeof updater === "function" ? updater(g) : updater));
|
|
@@ -23,6 +23,32 @@ Agents and streamed APIs elsewhere in the sandbox stay orthogonal: serverless sw
|
|
|
23
23
|
|
|
24
24
|
Sandbox rows reference **`authRef` / named env refs** — never literals in browser or config records. Scheduling uses the referenced API Registry row’s **`authRef`** merge rules identical to **`/api/workspace/test-source`**.
|
|
25
25
|
|
|
26
|
+
## Browser access (`browserAccess`)
|
|
27
|
+
|
|
28
|
+
`browserAccess` is a first-class boolean column on the sandbox row, surfaced as a single toggle in the record sidecar's **Environment & Network** section. It is locality-agnostic and agent-host-agnostic: the saved record carries the capability, and each execution path grants it through the mechanism that path actually understands.
|
|
29
|
+
|
|
30
|
+
**Deterministic normalization** — browser access implies outbound network. The sidecar toggle stamps `networkAllow: "true"` when browser access is switched on, and `POST /api/workspace/sandbox-run` enforces the same implication server-side (`networkAllow || browserAccess`), so rows patched via the API behave identically to rows saved in the UI.
|
|
31
|
+
|
|
32
|
+
**This is the product's existing agent browser primitive, surfaced — not a new system.** The upstream Paperclip server already grants any agent browser access through one boolean: the agent config's `chrome` primitive (`ui/src/components/agent-config-primitives.tsx` — "Enable Claude's Chrome integration by passing --chrome"), gated by the chrome-lease service (`server/src/services/chrome-lease.ts`) before `adapter.execute()`. The CMS profile contract likewise speaks `allowBrowserBridge` and execution mode `"browser"`. `browserAccess` is the same bit on the governed sandbox row, so rows stay portable to the upstream adapter registry without translation — exactly like the host slugs.
|
|
33
|
+
|
|
34
|
+
**Local (`local-agent-host`)** — when the row's bit is on, each host engages its **first-party** browser integration; the adapter never invents flags or writes host config it cannot verify against the upstream tool (the same rule the auth catalog follows for login subcommands):
|
|
35
|
+
|
|
36
|
+
| Lane | Hosts | Mechanism |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `native-flag` | Claude Code | `--chrome` — Claude's own Chrome integration, the same flag the upstream server adapter passes for the agent `chrome` primitive. |
|
|
39
|
+
| `native-flag` | Codex | `--enable browser_use --enable in_app_browser` (with `--sandbox workspace-write`). |
|
|
40
|
+
| `env-signal` | Cursor, Gemini, Qwen, OpenCode, Pi, Hermes, OpenClaw Gateway | The host receives `GROWTHUB_SANDBOX_BROWSER_ACCESS=1` (mirroring the upstream browser-isolation context); whatever browser integration the operator has configured in that host honors the row's setting. |
|
|
41
|
+
|
|
42
|
+
The lane engaged for a run is recorded in `adapterMeta.browserLane`, and the run-console record projection surfaces `context.browserAccess` plus the full `adapterMeta`, so every run shows its browser proof. No host-global config (`~/.claude`, `~/.codex`, …) is ever mutated.
|
|
43
|
+
|
|
44
|
+
**Orchestration graph** — this is why browser access is node-level and host-agnostic with zero extra configuration: `thinAdapter` and `ai-agent` nodes execute through this same host catalog, so every node inherits the row's browser grant no matter which host runs it (subagent nodes through the existing node-level Network gate; orchestrator and synthesis phases directly).
|
|
45
|
+
|
|
46
|
+
One deliberate decision, stated explicitly: **Codex `workspace-write` on `networkAllow` alone is intentional.** Codex's `read-only` sandbox blocks all outbound network, so `workspace-write` is the least-privileged Codex mode where the row's network grant can take effect — and writes are confined to the sealed ephemeral workdir the adapter spawns into, never the operator's repo. Browser flags remain gated on `browserAccess` only; network alone never opens a browser.
|
|
47
|
+
|
|
48
|
+
**Local (`local-process`)** and every other adapter — the sealed RunRequest carries `browserAccess: boolean`, and the env contract publishes `GROWTHUB_SANDBOX_BROWSER_ACCESS=1|0` alongside `GROWTHUB_SANDBOX_NET_ALLOW(LIST)`, so any script or drop-zone adapter honors the row's setting without knowing about specific hosts.
|
|
49
|
+
|
|
50
|
+
**Serverless** — the `growthub-sandbox-run-v1` envelope carries `sandbox.browserAccess` (plus `networkAllow` / `allowList`), so a workflow upgraded from local to serverless keeps the identical capability contract: the Edge/QStash/cron handler reads one boolean and grants its own runtime's browser (e.g. a remote browser pool or hosted agent's browser tool). No host-specific knowledge crosses the wire — slugs and booleans only, never secrets.
|
|
51
|
+
|
|
26
52
|
## Not a widget source
|
|
27
53
|
|
|
28
54
|
Workspace Builder excludes **`sandbox-environment`** from View widget bindings (execution records, not tabular KPI sources). See **`data-sources-api-registry.md`** in this folder.
|