@growthub/cli 0.14.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/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 +14 -1
- 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/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 +13 -4
- 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-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-data-model.js +1 -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/package.json +1 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* NEVER written to `growthub.config.json`.
|
|
16
16
|
*
|
|
17
17
|
* Request body:
|
|
18
|
-
* { objectId: string, name: string }
|
|
18
|
+
* { objectId: string, name: string, agentHost?: string }
|
|
19
19
|
*
|
|
20
20
|
* Response:
|
|
21
21
|
* {
|
|
@@ -49,6 +49,7 @@ async function POST(request) {
|
|
|
49
49
|
|
|
50
50
|
const objectId = typeof body?.objectId === "string" ? body.objectId.trim() : "";
|
|
51
51
|
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
52
|
+
const agentHost = typeof body?.agentHost === "string" ? body.agentHost.trim() : "";
|
|
52
53
|
if (!objectId || !name) {
|
|
53
54
|
return NextResponse.json(
|
|
54
55
|
{ ok: false, error: "objectId and name are required" },
|
|
@@ -57,7 +58,7 @@ async function POST(request) {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
try {
|
|
60
|
-
const result = await runAgentLogin({ objectId, name });
|
|
61
|
+
const result = await runAgentLogin({ objectId, name, agentHost });
|
|
61
62
|
return NextResponse.json(result);
|
|
62
63
|
} catch (error) {
|
|
63
64
|
return NextResponse.json(
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* host's `notes` string.
|
|
11
11
|
*
|
|
12
12
|
* Request body:
|
|
13
|
-
* { objectId: string, name: string }
|
|
13
|
+
* { objectId: string, name: string, agentHost?: string }
|
|
14
14
|
*
|
|
15
15
|
* Response:
|
|
16
16
|
* {
|
|
@@ -42,6 +42,7 @@ async function POST(request) {
|
|
|
42
42
|
|
|
43
43
|
const objectId = typeof body?.objectId === "string" ? body.objectId.trim() : "";
|
|
44
44
|
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
45
|
+
const agentHost = typeof body?.agentHost === "string" ? body.agentHost.trim() : "";
|
|
45
46
|
if (!objectId || !name) {
|
|
46
47
|
return NextResponse.json(
|
|
47
48
|
{ ok: false, error: "objectId and name are required" },
|
|
@@ -50,7 +51,7 @@ async function POST(request) {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
try {
|
|
53
|
-
const result = await runAgentLogout({ objectId, name });
|
|
54
|
+
const result = await runAgentLogout({ objectId, name, agentHost });
|
|
54
55
|
return NextResponse.json(result);
|
|
55
56
|
} catch (error) {
|
|
56
57
|
return NextResponse.json(
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* load.
|
|
21
21
|
*
|
|
22
22
|
* Request body:
|
|
23
|
-
* { objectId: string, name: string }
|
|
23
|
+
* { objectId: string, name: string, agentHost?: string }
|
|
24
24
|
*
|
|
25
25
|
* Response (success):
|
|
26
26
|
* {
|
|
@@ -52,6 +52,7 @@ async function POST(request) {
|
|
|
52
52
|
|
|
53
53
|
const objectId = typeof body?.objectId === "string" ? body.objectId.trim() : "";
|
|
54
54
|
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
55
|
+
const agentHost = typeof body?.agentHost === "string" ? body.agentHost.trim() : "";
|
|
55
56
|
if (!objectId || !name) {
|
|
56
57
|
return NextResponse.json(
|
|
57
58
|
{ ok: false, error: "objectId and name are required" },
|
|
@@ -60,7 +61,7 @@ async function POST(request) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
try {
|
|
63
|
-
const result = await checkAgentStatus({ objectId, name });
|
|
64
|
+
const result = await checkAgentStatus({ objectId, name, agentHost });
|
|
64
65
|
return NextResponse.json(result);
|
|
65
66
|
} catch (error) {
|
|
66
67
|
return NextResponse.json(
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
* envRefsMissing: string[],
|
|
44
44
|
* networkAllow: boolean,
|
|
45
45
|
* allowList: string[],
|
|
46
|
+
* browserAccess: boolean, // first-class browser capability (implies networkAllow)
|
|
46
47
|
* adapterMeta?: Record<string, unknown>
|
|
47
48
|
* }
|
|
48
49
|
* }
|
|
@@ -173,6 +174,7 @@ async function runServerlessScheduler({
|
|
|
173
174
|
timeoutMs,
|
|
174
175
|
networkAllow,
|
|
175
176
|
allowList,
|
|
177
|
+
browserAccess,
|
|
176
178
|
envRefSlugs,
|
|
177
179
|
envRefsResolved,
|
|
178
180
|
envRefsMissing
|
|
@@ -251,6 +253,7 @@ async function runServerlessScheduler({
|
|
|
251
253
|
timeoutMs,
|
|
252
254
|
networkAllow,
|
|
253
255
|
allowList,
|
|
256
|
+
browserAccess,
|
|
254
257
|
envRefSlugs,
|
|
255
258
|
envRefsResolved,
|
|
256
259
|
envRefsMissing
|
|
@@ -353,6 +356,7 @@ function buildRunResponse({
|
|
|
353
356
|
envRefsMissing,
|
|
354
357
|
networkAllow,
|
|
355
358
|
allowList,
|
|
359
|
+
browserAccess,
|
|
356
360
|
result,
|
|
357
361
|
timeoutMs,
|
|
358
362
|
row,
|
|
@@ -384,6 +388,7 @@ function buildRunResponse({
|
|
|
384
388
|
envRefsMissing,
|
|
385
389
|
networkAllow,
|
|
386
390
|
allowList,
|
|
391
|
+
browserAccess,
|
|
387
392
|
adapterMeta: result.adapterMeta || null
|
|
388
393
|
};
|
|
389
394
|
if (row && (row.resolverTemplateId || row.connectorKind || row.executionLane)) {
|
|
@@ -497,7 +502,11 @@ async function executeSandboxRun(body, { emit } = {}) {
|
|
|
497
502
|
let adapterId = (typeof rowForRun.adapter === "string" && rowForRun.adapter.trim()) ? rowForRun.adapter.trim() : DEFAULT_SANDBOX_ADAPTER;
|
|
498
503
|
const agentHost = typeof rowForRun.agentHost === "string" ? rowForRun.agentHost.trim() : "";
|
|
499
504
|
const schedulerRegistryId = typeof rowForRun.schedulerRegistryId === "string" ? rowForRun.schedulerRegistryId.trim() : "";
|
|
500
|
-
const
|
|
505
|
+
const browserAccess = coerceBoolean(rowForRun.browserAccess);
|
|
506
|
+
// Browser access implies outbound network — the same deterministic
|
|
507
|
+
// normalization the sidecar toggle applies, enforced server-side so
|
|
508
|
+
// rows patched via the API behave identically to rows saved in the UI.
|
|
509
|
+
const networkAllow = coerceBoolean(rowForRun.networkAllow) || browserAccess;
|
|
501
510
|
const allowList = parseSandboxAllowList(rowForRun.allowList);
|
|
502
511
|
const envRefSlugs = parseSandboxEnvRefs(rowForRun.envRefs);
|
|
503
512
|
const command = typeof rowForRun.command === "string" ? rowForRun.command : "";
|
|
@@ -576,6 +585,7 @@ async function executeSandboxRun(body, { emit } = {}) {
|
|
|
576
585
|
envRefsResolved,
|
|
577
586
|
networkAllow,
|
|
578
587
|
allowList,
|
|
588
|
+
browserAccess,
|
|
579
589
|
instructions,
|
|
580
590
|
command,
|
|
581
591
|
timeoutMs,
|
|
@@ -607,6 +617,7 @@ async function executeSandboxRun(body, { emit } = {}) {
|
|
|
607
617
|
timeoutMs,
|
|
608
618
|
networkAllow,
|
|
609
619
|
allowList,
|
|
620
|
+
browserAccess,
|
|
610
621
|
envRefSlugs,
|
|
611
622
|
envRefsResolved,
|
|
612
623
|
envRefsMissing
|
|
@@ -640,6 +651,7 @@ async function executeSandboxRun(body, { emit } = {}) {
|
|
|
640
651
|
timeoutMs,
|
|
641
652
|
networkAllow,
|
|
642
653
|
allowList,
|
|
654
|
+
browserAccess,
|
|
643
655
|
env,
|
|
644
656
|
envRefSlugs,
|
|
645
657
|
envRefsMissing,
|
|
@@ -680,6 +692,7 @@ async function executeSandboxRun(body, { emit } = {}) {
|
|
|
680
692
|
envRefsMissing,
|
|
681
693
|
networkAllow,
|
|
682
694
|
allowList,
|
|
695
|
+
browserAccess,
|
|
683
696
|
result,
|
|
684
697
|
timeoutMs,
|
|
685
698
|
row: rowForRun,
|
|
@@ -255,7 +255,7 @@ function WorkspaceHelperSetupModal({ workspaceConfig, open, onClose, onSaved })
|
|
|
255
255
|
/>
|
|
256
256
|
serverless
|
|
257
257
|
</label>
|
|
258
|
-
<small>
|
|
258
|
+
<small>Choose local execution or a scheduled serverless run.</small>
|
|
259
259
|
</div>
|
|
260
260
|
<div className="workspace-helper-setup-field-stack">
|
|
261
261
|
<label>
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
import { Check, Plus, Trash2 } from "lucide-react";
|
|
5
5
|
import { HOST_AUTH_CATALOG } from "@/lib/sandbox-agent-host-catalog";
|
|
6
|
+
import { SandboxAgentAuthPanel } from "./SandboxAgentAuthPanel.jsx";
|
|
7
|
+
import { isSandboxLocalAgentHost } from "@/lib/sandbox-agent-auth-eligibility";
|
|
8
|
+
|
|
9
|
+
const EMPTY_AGENT_AUTH_PATCH = {
|
|
10
|
+
agentAuthStatus: "",
|
|
11
|
+
agentAuthProvider: "",
|
|
12
|
+
agentAuthLastChecked: "",
|
|
13
|
+
agentAuthLastExitCode: "",
|
|
14
|
+
agentAuthLastMessage: "",
|
|
15
|
+
agentAuthLastLoginUrl: ""
|
|
16
|
+
};
|
|
6
17
|
|
|
7
18
|
function getHostOptions() {
|
|
8
19
|
return Object.entries(HOST_AUTH_CATALOG || {}).map(([slug, host]) => ({
|
|
@@ -26,6 +37,17 @@ function withRecordRef(patch, objectId, rowName, nodeId) {
|
|
|
26
37
|
};
|
|
27
38
|
}
|
|
28
39
|
|
|
40
|
+
function buildSubagentAuthDraft(sandboxRow, config) {
|
|
41
|
+
const agentHost = String(config?.agentHost || sandboxRow?.agentHost || "").trim();
|
|
42
|
+
if (!agentHost) return null;
|
|
43
|
+
return {
|
|
44
|
+
...(sandboxRow || {}),
|
|
45
|
+
runLocality: "local",
|
|
46
|
+
adapter: "local-agent-host",
|
|
47
|
+
agentHost
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
function WorkflowCheckbox({ checked, disabled, onChange, children, title }) {
|
|
30
52
|
return (
|
|
31
53
|
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline dm-workflow-check" title={title}>
|
|
@@ -139,7 +161,7 @@ function patchSwarmConfig(graph, patch) {
|
|
|
139
161
|
return { ...graph, swarm: { ...base, ...patch } };
|
|
140
162
|
}
|
|
141
163
|
|
|
142
|
-
export function AgentSwarmPanel({ graph, objectId, rowName, onGraphChange, disabled }) {
|
|
164
|
+
export function AgentSwarmPanel({ graph, objectId, rowName, sandboxRow, onSandboxRowPatch, onGraphChange, disabled }) {
|
|
143
165
|
const hostOptions = useMemo(getHostOptions, []);
|
|
144
166
|
if (!graph || typeof graph !== "object") return null;
|
|
145
167
|
|
|
@@ -154,6 +176,21 @@ export function AgentSwarmPanel({ graph, objectId, rowName, onGraphChange, disab
|
|
|
154
176
|
onGraphChange?.(updater);
|
|
155
177
|
}
|
|
156
178
|
|
|
179
|
+
function patchSubagentHost(nodeId, agentHost) {
|
|
180
|
+
const nextHost = String(agentHost || "").trim();
|
|
181
|
+
patchGraph((g) => patchSubagent(g, nodeId, {
|
|
182
|
+
agentHost: nextHost,
|
|
183
|
+
...(nextHost ? { adapter: "local-agent-host" } : {})
|
|
184
|
+
}, objectId, rowName));
|
|
185
|
+
if (nextHost && typeof onSandboxRowPatch === "function") {
|
|
186
|
+
onSandboxRowPatch({
|
|
187
|
+
adapter: "local-agent-host",
|
|
188
|
+
agentHost: nextHost,
|
|
189
|
+
...EMPTY_AGENT_AUTH_PATCH
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
157
194
|
return (
|
|
158
195
|
<div className="dm-orchestration-config dm-agent-swarm-panel">
|
|
159
196
|
<div className="dm-orchestration-config__pane">
|
|
@@ -187,6 +224,7 @@ export function AgentSwarmPanel({ graph, objectId, rowName, onGraphChange, disab
|
|
|
187
224
|
)}
|
|
188
225
|
{subagents.map((node) => {
|
|
189
226
|
const cfg = node.config || {};
|
|
227
|
+
const subagentAuthDraft = buildSubagentAuthDraft(sandboxRow, cfg);
|
|
190
228
|
return (
|
|
191
229
|
<div key={node.id} className="dm-agent-swarm-panel__subagent">
|
|
192
230
|
<div className="dm-agent-swarm-panel__row">
|
|
@@ -252,7 +290,7 @@ export function AgentSwarmPanel({ graph, objectId, rowName, onGraphChange, disab
|
|
|
252
290
|
<select
|
|
253
291
|
value={cfg.agentHost || ""}
|
|
254
292
|
disabled={disabled}
|
|
255
|
-
onChange={(e) =>
|
|
293
|
+
onChange={(e) => patchSubagentHost(node.id, e.target.value)}
|
|
256
294
|
>
|
|
257
295
|
<option value="">Inherit</option>
|
|
258
296
|
{hostOptions.map((opt) => (
|
|
@@ -260,6 +298,15 @@ export function AgentSwarmPanel({ graph, objectId, rowName, onGraphChange, disab
|
|
|
260
298
|
))}
|
|
261
299
|
</select>
|
|
262
300
|
</label>
|
|
301
|
+
{subagentAuthDraft && isSandboxLocalAgentHost(subagentAuthDraft) && (
|
|
302
|
+
<SandboxAgentAuthPanel
|
|
303
|
+
objectId={objectId}
|
|
304
|
+
rowName={rowName}
|
|
305
|
+
draft={subagentAuthDraft}
|
|
306
|
+
disabled={disabled || typeof onSandboxRowPatch !== "function"}
|
|
307
|
+
onPatchDraft={onSandboxRowPatch}
|
|
308
|
+
/>
|
|
309
|
+
)}
|
|
263
310
|
<WorkflowCheckbox
|
|
264
311
|
checked={cfg.required !== false}
|
|
265
312
|
disabled={disabled}
|
|
@@ -647,6 +647,14 @@ function SandboxTraceFieldButton({ label, value, disabled, onOpen }) {
|
|
|
647
647
|
);
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
+
// Human labels for the per-host browser lanes declared in the
|
|
651
|
+
// local-agent-host catalog — surfaced so the operator's mental model matches
|
|
652
|
+
// exactly what the adapter does under the hood when browserAccess is on.
|
|
653
|
+
const BROWSER_LANE_LABELS = {
|
|
654
|
+
"native-flag": "browser enabled through the host CLI's first-party browser integration flags.",
|
|
655
|
+
"env-signal": "host receives GROWTHUB_SANDBOX_BROWSER_ACCESS=1 — its own configured browser integration honors this setting."
|
|
656
|
+
};
|
|
657
|
+
|
|
650
658
|
function SandboxRecordFields({
|
|
651
659
|
draft,
|
|
652
660
|
setDraft,
|
|
@@ -705,8 +713,21 @@ function SandboxRecordFields({
|
|
|
705
713
|
return { ...fields, ...EMPTY_AGENT_AUTH_PATCH };
|
|
706
714
|
}
|
|
707
715
|
|
|
716
|
+
function defaultSchedulerRegistryId() {
|
|
717
|
+
const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
|
|
718
|
+
for (const object of objects) {
|
|
719
|
+
if (object?.objectType !== "api-registry") continue;
|
|
720
|
+
const row = (object.rows || []).find((r) => String(r?.integrationId || "").trim());
|
|
721
|
+
if (row) return String(row.integrationId || "").trim();
|
|
722
|
+
}
|
|
723
|
+
return "";
|
|
724
|
+
}
|
|
725
|
+
|
|
708
726
|
function setRunLocality(next) {
|
|
709
727
|
const fields = { runLocality: next };
|
|
728
|
+
if (next === "serverless") {
|
|
729
|
+
fields.schedulerRegistryId = String(draft.schedulerRegistryId || "").trim() || defaultSchedulerRegistryId();
|
|
730
|
+
}
|
|
710
731
|
if (next === "serverless" && ["local-agent-host", "local-intelligence"].includes(String(draft.adapter || "").trim())) {
|
|
711
732
|
fields.adapter = "local-process";
|
|
712
733
|
fields.agentHost = "";
|
|
@@ -724,6 +745,8 @@ function SandboxRecordFields({
|
|
|
724
745
|
}
|
|
725
746
|
|
|
726
747
|
const netOn = ["true", "1", "on", "yes"].includes(String(draft.networkAllow || "").trim().toLowerCase());
|
|
748
|
+
const browserOn = ["true", "1", "on", "yes"].includes(String(draft.browserAccess || "").trim().toLowerCase());
|
|
749
|
+
const browserHostMeta = (selectedAdapterMeta?.hostCatalog || []).find((h) => h.slug === String(draft.agentHost || "").trim());
|
|
727
750
|
|
|
728
751
|
// Same cockpit interface + mental model as the API Registry lane, driven by
|
|
729
752
|
// the serverless/scheduling/persistence derivation. Steps are status-only
|
|
@@ -735,6 +758,7 @@ function SandboxRecordFields({
|
|
|
735
758
|
persistenceAdapters: serverlessSignals.persistenceAdapters,
|
|
736
759
|
inlineEditing: true,
|
|
737
760
|
});
|
|
761
|
+
const showServerlessUpgrade = String(draft.adapter || "").trim() !== "local-intelligence";
|
|
738
762
|
function handleServerlessAction(action) {
|
|
739
763
|
if (!action) return;
|
|
740
764
|
if (action.id === "toggle-locality") setRunLocality(serverlessState.isServerless ? "local" : "serverless");
|
|
@@ -743,12 +767,14 @@ function SandboxRecordFields({
|
|
|
743
767
|
|
|
744
768
|
return (
|
|
745
769
|
<div className="dm-sandbox-config">
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
770
|
+
{showServerlessUpgrade && (
|
|
771
|
+
<ApiRegistryCreationCockpit
|
|
772
|
+
state={serverlessState}
|
|
773
|
+
onAction={handleServerlessAction}
|
|
774
|
+
disabled={!table.mutable || saving}
|
|
775
|
+
eyebrow={serverlessState.isServerless ? "Serverless workflow" : "Workflow runtime"}
|
|
776
|
+
/>
|
|
777
|
+
)}
|
|
752
778
|
<DrawerSection title="Identity & Mode">
|
|
753
779
|
<label className="dm-record-field">
|
|
754
780
|
<span>Name</span>
|
|
@@ -791,7 +817,7 @@ function SandboxRecordFields({
|
|
|
791
817
|
onChange={setRunLocality}
|
|
792
818
|
/>
|
|
793
819
|
<p className="dm-cell-empty" style={{ fontSize: 11, marginTop: 6 }}>
|
|
794
|
-
|
|
820
|
+
Choose local execution or a scheduled serverless run.
|
|
795
821
|
</p>
|
|
796
822
|
|
|
797
823
|
{locality === "serverless" && table.objectId && (
|
|
@@ -883,7 +909,7 @@ function SandboxRecordFields({
|
|
|
883
909
|
</label>
|
|
884
910
|
|
|
885
911
|
<p className="dm-cell-empty" style={{ fontSize: 11, marginTop: 0 }}>
|
|
886
|
-
Uses <strong>Instructions</strong> + <strong>Command</strong> as the task payload.
|
|
912
|
+
Uses <strong>Instructions</strong> + <strong>Command</strong> as the task payload. With browser access off, tool intents stay proposals. With browser access on, browser tool intents execute through the local browser bridge before the final JSON response is returned.
|
|
887
913
|
</p>
|
|
888
914
|
</div>
|
|
889
915
|
)}
|
|
@@ -921,10 +947,12 @@ function SandboxRecordFields({
|
|
|
921
947
|
</div>
|
|
922
948
|
|
|
923
949
|
<ToggleField
|
|
924
|
-
checked={netOn}
|
|
925
|
-
disabled={!table.mutable || saving}
|
|
950
|
+
checked={netOn || browserOn}
|
|
951
|
+
disabled={!table.mutable || saving || (browserOn && !netOn)}
|
|
926
952
|
label="Network allow-list mode"
|
|
927
|
-
description=
|
|
953
|
+
description={browserOn && !netOn
|
|
954
|
+
? "Network enabled by browser access — the run route grants it even though this row's networkAllow is off. Turn browser access off to control network independently."
|
|
955
|
+
: "When enabled, local runs honor GROWTHUB_SANDBOX_NET_* and the allow list below."}
|
|
928
956
|
onChange={(on) => patchFields({ networkAllow: on ? "true" : "false" })}
|
|
929
957
|
/>
|
|
930
958
|
|
|
@@ -937,6 +965,21 @@ function SandboxRecordFields({
|
|
|
937
965
|
onBlur={(event) => patchFields({ allowList: event.target.value })}
|
|
938
966
|
/>
|
|
939
967
|
</label>
|
|
968
|
+
|
|
969
|
+
<ToggleField
|
|
970
|
+
checked={browserOn}
|
|
971
|
+
disabled={!table.mutable || saving}
|
|
972
|
+
label="Browser access"
|
|
973
|
+
description="Allows this sandbox to use a real browser. Also enables network. Local intelligence uses the Playwright browser bridge; Codex/Claude use their native browser modes."
|
|
974
|
+
onChange={(on) => patchFields(on
|
|
975
|
+
? { browserAccess: "true", networkAllow: "true" }
|
|
976
|
+
: { browserAccess: "false" })}
|
|
977
|
+
/>
|
|
978
|
+
{browserOn && String(draft.adapter || "").trim() === "local-agent-host" && browserHostMeta && (
|
|
979
|
+
<p className="dm-cell-empty" style={{ fontSize: 11, marginTop: 4 }}>
|
|
980
|
+
{browserHostMeta.label}: {BROWSER_LANE_LABELS[browserHostMeta.browserLane] || BROWSER_LANE_LABELS["env-signal"]}
|
|
981
|
+
</p>
|
|
982
|
+
)}
|
|
940
983
|
</DrawerSection>
|
|
941
984
|
|
|
942
985
|
<DrawerSection title="Prompt & Limits">
|
|
@@ -20,6 +20,12 @@ const LOCAL_AGENT_ADAPTERS = [
|
|
|
20
20
|
{ value: "local-agent-host", label: "Local agent host" },
|
|
21
21
|
{ value: "local-intelligence", label: "Local intelligence" }
|
|
22
22
|
];
|
|
23
|
+
const LOCAL_INTELLIGENCE_MODE_OPTIONS = [
|
|
24
|
+
{ value: "ollama", label: "ollama (OLLAMA_BASE_URL + /v1/chat/completions)" },
|
|
25
|
+
{ value: "lmstudio", label: "lmstudio (LMSTUDIO_BASE_URL)" },
|
|
26
|
+
{ value: "vllm", label: "vllm (VLLM_BASE_URL required)" },
|
|
27
|
+
{ value: "custom-openai-compatible", label: "custom (use Chat completions URL above)" }
|
|
28
|
+
];
|
|
23
29
|
const EMPTY_AGENT_AUTH_PATCH = {
|
|
24
30
|
agentAuthStatus: "",
|
|
25
31
|
agentAuthProvider: "",
|
|
@@ -427,9 +433,9 @@ function LocalAgentHostControls({
|
|
|
427
433
|
onSandboxRowPatch
|
|
428
434
|
}) {
|
|
429
435
|
const row = sandboxRow && typeof sandboxRow === "object" ? sandboxRow : {};
|
|
430
|
-
const runLocality = String(row.runLocality || "local").trim().toLowerCase() === "serverless" ? "serverless" : "local";
|
|
431
436
|
const adapter = String(row.adapter || "local-process").trim() || "local-process";
|
|
432
437
|
const agentHost = String(row.agentHost || "").trim();
|
|
438
|
+
const browserOn = ["true", "1", "on", "yes"].includes(String(row.browserAccess || "").trim().toLowerCase());
|
|
433
439
|
const hostOptions = getAgentHostOptions();
|
|
434
440
|
const canPatch = typeof onSandboxRowPatch === "function";
|
|
435
441
|
|
|
@@ -444,36 +450,9 @@ function LocalAgentHostControls({
|
|
|
444
450
|
return (
|
|
445
451
|
<div className="dm-orchestration-config__section dm-workflow-agent-runtime">
|
|
446
452
|
<span>Local agent runtime</span>
|
|
447
|
-
<div className="dm-sandbox-locality-toggle" role="group" aria-label="Run locality">
|
|
448
|
-
{["local", "serverless"].map((mode) => (
|
|
449
|
-
<button
|
|
450
|
-
key={mode}
|
|
451
|
-
type="button"
|
|
452
|
-
className={runLocality === mode ? "is-active" : ""}
|
|
453
|
-
disabled={disabled || !canPatch}
|
|
454
|
-
onClick={() => {
|
|
455
|
-
const fields = { runLocality: mode };
|
|
456
|
-
if (mode === "serverless" && ["local-agent-host", "local-intelligence"].includes(adapter)) {
|
|
457
|
-
fields.adapter = "local-process";
|
|
458
|
-
fields.agentHost = "";
|
|
459
|
-
patchWithClearedAgentAuth(fields);
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
patch(fields);
|
|
463
|
-
}}
|
|
464
|
-
>
|
|
465
|
-
{mode === "local" ? "Local" : "Serverless"}
|
|
466
|
-
</button>
|
|
467
|
-
))}
|
|
468
|
-
</div>
|
|
469
453
|
<p className="dm-orchestration-config__hint">
|
|
470
454
|
Same runtime fields as the Data Model sandbox sidecar. Local agent host uses the Paperclip thin adapter on this machine.
|
|
471
455
|
</p>
|
|
472
|
-
{runLocality === "serverless" && (
|
|
473
|
-
<p className="dm-orchestration-config__hint">
|
|
474
|
-
Serverless delegates execution to the configured scheduler/API Registry row; local CLI auth is not used.
|
|
475
|
-
</p>
|
|
476
|
-
)}
|
|
477
456
|
<label className="dm-orchestration-config__field">
|
|
478
457
|
<span>Execution adapter</span>
|
|
479
458
|
<select
|
|
@@ -482,6 +461,7 @@ function LocalAgentHostControls({
|
|
|
482
461
|
onChange={(event) => {
|
|
483
462
|
const nextAdapter = event.target.value;
|
|
484
463
|
patchWithClearedAgentAuth({
|
|
464
|
+
runLocality: "local",
|
|
485
465
|
adapter: nextAdapter,
|
|
486
466
|
agentHost: nextAdapter === "local-agent-host" ? (agentHost || "claude_local") : ""
|
|
487
467
|
});
|
|
@@ -492,13 +472,17 @@ function LocalAgentHostControls({
|
|
|
492
472
|
))}
|
|
493
473
|
</select>
|
|
494
474
|
</label>
|
|
495
|
-
{
|
|
475
|
+
{adapter === "local-agent-host" && (
|
|
496
476
|
<label className="dm-orchestration-config__field">
|
|
497
477
|
<span>Agent host (Paperclip)</span>
|
|
498
478
|
<select
|
|
499
479
|
value={agentHost}
|
|
500
480
|
disabled={disabled || !canPatch}
|
|
501
|
-
onChange={(event) => patchWithClearedAgentAuth({
|
|
481
|
+
onChange={(event) => patchWithClearedAgentAuth({
|
|
482
|
+
runLocality: "local",
|
|
483
|
+
adapter: "local-agent-host",
|
|
484
|
+
agentHost: event.target.value
|
|
485
|
+
})}
|
|
502
486
|
>
|
|
503
487
|
<option value="">Select host...</option>
|
|
504
488
|
{hostOptions.map((item) => (
|
|
@@ -507,7 +491,7 @@ function LocalAgentHostControls({
|
|
|
507
491
|
</select>
|
|
508
492
|
</label>
|
|
509
493
|
)}
|
|
510
|
-
{
|
|
494
|
+
{adapter === "local-agent-host" && isSandboxLocalAgentHost(row) && (
|
|
511
495
|
<SandboxAgentAuthPanel
|
|
512
496
|
objectId={objectId}
|
|
513
497
|
rowName={rowName}
|
|
@@ -516,10 +500,63 @@ function LocalAgentHostControls({
|
|
|
516
500
|
onPatchDraft={patch}
|
|
517
501
|
/>
|
|
518
502
|
)}
|
|
503
|
+
{adapter === "local-intelligence" && (
|
|
504
|
+
<div className="dm-sandbox-local-intel">
|
|
505
|
+
<label className="dm-orchestration-config__field">
|
|
506
|
+
<span>Concrete model id</span>
|
|
507
|
+
<input
|
|
508
|
+
value={row.localModel || ""}
|
|
509
|
+
disabled={disabled || !canPatch}
|
|
510
|
+
placeholder="gemma3:4b"
|
|
511
|
+
onChange={(event) => patch({ runLocality: "local", localModel: event.target.value })}
|
|
512
|
+
/>
|
|
513
|
+
</label>
|
|
514
|
+
<label className="dm-orchestration-config__field">
|
|
515
|
+
<span>Chat completions URL (optional)</span>
|
|
516
|
+
<input
|
|
517
|
+
value={row.localEndpoint || ""}
|
|
518
|
+
disabled={disabled || !canPatch}
|
|
519
|
+
placeholder="http://127.0.0.1:11434/v1/chat/completions"
|
|
520
|
+
onChange={(event) => patch({ runLocality: "local", localEndpoint: event.target.value })}
|
|
521
|
+
/>
|
|
522
|
+
</label>
|
|
523
|
+
<label className="dm-orchestration-config__field">
|
|
524
|
+
<span>Resolver mode</span>
|
|
525
|
+
<select
|
|
526
|
+
value={String(row.intelligenceAdapterMode || "ollama").trim().toLowerCase()}
|
|
527
|
+
disabled={disabled || !canPatch}
|
|
528
|
+
onChange={(event) => patch({ runLocality: "local", intelligenceAdapterMode: event.target.value })}
|
|
529
|
+
>
|
|
530
|
+
{LOCAL_INTELLIGENCE_MODE_OPTIONS.map((item) => (
|
|
531
|
+
<option key={item.value} value={item.value}>{item.label}</option>
|
|
532
|
+
))}
|
|
533
|
+
</select>
|
|
534
|
+
</label>
|
|
535
|
+
<p className="dm-orchestration-config__hint">
|
|
536
|
+
Uses Instructions + Command as the task payload. With sandbox browser access off, tool intents stay proposals. With browser access on, browser tool intents execute through the local browser bridge before the final JSON response is returned.
|
|
537
|
+
</p>
|
|
538
|
+
{browserOn && (
|
|
539
|
+
<p className="dm-orchestration-config__hint">
|
|
540
|
+
This workflow's AI-agent nodes inherit browser access only when their node-level Network permission is enabled.
|
|
541
|
+
</p>
|
|
542
|
+
)}
|
|
543
|
+
</div>
|
|
544
|
+
)}
|
|
519
545
|
</div>
|
|
520
546
|
);
|
|
521
547
|
}
|
|
522
548
|
|
|
549
|
+
function buildNodeAgentAuthDraft(sandboxRow, config) {
|
|
550
|
+
const agentHost = String(config?.agentHost || sandboxRow?.agentHost || "").trim();
|
|
551
|
+
if (!agentHost) return null;
|
|
552
|
+
return {
|
|
553
|
+
...(sandboxRow || {}),
|
|
554
|
+
runLocality: "local",
|
|
555
|
+
adapter: "local-agent-host",
|
|
556
|
+
agentHost
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
523
560
|
export function OrchestrationNodeConfigPanel({
|
|
524
561
|
node,
|
|
525
562
|
onConfigChange,
|
|
@@ -577,6 +614,28 @@ export function OrchestrationNodeConfigPanel({
|
|
|
577
614
|
|
|
578
615
|
const registryConnected = isApiRegistryTestSuccessful(registryRow);
|
|
579
616
|
const responseMode = config.responseMode || config.mode || "json";
|
|
617
|
+
const nodeAgentAuthDraft = type === "ai-agent" ? buildNodeAgentAuthDraft(sandboxRow, config) : null;
|
|
618
|
+
const canPatchSandboxRow = typeof onSandboxRowPatch === "function";
|
|
619
|
+
const sandboxBrowserOn = ["true", "1", "on", "yes"].includes(String(sandboxRow?.browserAccess || "").trim().toLowerCase());
|
|
620
|
+
const sandboxAdapter = String(sandboxRow?.adapter || "").trim();
|
|
621
|
+
const nodeAdapter = String(config.adapter || "").trim() || sandboxAdapter;
|
|
622
|
+
const nodeUsesLocalIntelligence = nodeAdapter === "local-intelligence";
|
|
623
|
+
|
|
624
|
+
function patchNodeAgentHost(agentHost) {
|
|
625
|
+
const nextHost = String(agentHost || "").trim();
|
|
626
|
+
patchConfig({
|
|
627
|
+
agentHost: nextHost,
|
|
628
|
+
...(nextHost ? { adapter: "local-agent-host" } : {})
|
|
629
|
+
});
|
|
630
|
+
if (nextHost && canPatchSandboxRow) {
|
|
631
|
+
onSandboxRowPatch({
|
|
632
|
+
runLocality: "local",
|
|
633
|
+
adapter: "local-agent-host",
|
|
634
|
+
agentHost: nextHost,
|
|
635
|
+
...EMPTY_AGENT_AUTH_PATCH
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
580
639
|
|
|
581
640
|
return (
|
|
582
641
|
<div className="dm-orchestration-config">
|
|
@@ -964,7 +1023,7 @@ export function OrchestrationNodeConfigPanel({
|
|
|
964
1023
|
<select
|
|
965
1024
|
value={config.agentHost || ""}
|
|
966
1025
|
disabled={disabled}
|
|
967
|
-
onChange={(e) =>
|
|
1026
|
+
onChange={(e) => patchNodeAgentHost(e.target.value)}
|
|
968
1027
|
>
|
|
969
1028
|
<option value="">Inherit</option>
|
|
970
1029
|
{Object.entries(HOST_AUTH_CATALOG || {}).map(([slug, host]) => (
|
|
@@ -972,6 +1031,15 @@ export function OrchestrationNodeConfigPanel({
|
|
|
972
1031
|
))}
|
|
973
1032
|
</select>
|
|
974
1033
|
</label>
|
|
1034
|
+
{nodeAgentAuthDraft && isSandboxLocalAgentHost(nodeAgentAuthDraft) && (
|
|
1035
|
+
<SandboxAgentAuthPanel
|
|
1036
|
+
objectId={objectId}
|
|
1037
|
+
rowName={rowName}
|
|
1038
|
+
draft={nodeAgentAuthDraft}
|
|
1039
|
+
disabled={disabled || !canPatchSandboxRow}
|
|
1040
|
+
onPatchDraft={onSandboxRowPatch}
|
|
1041
|
+
/>
|
|
1042
|
+
)}
|
|
975
1043
|
<WorkflowCheckbox
|
|
976
1044
|
checked={config.required !== false}
|
|
977
1045
|
disabled={disabled}
|
|
@@ -982,10 +1050,12 @@ export function OrchestrationNodeConfigPanel({
|
|
|
982
1050
|
<WorkflowCheckbox
|
|
983
1051
|
checked={config.networkAccess === true}
|
|
984
1052
|
disabled={disabled}
|
|
985
|
-
title=
|
|
1053
|
+
title={sandboxBrowserOn && nodeUsesLocalIntelligence
|
|
1054
|
+
? "Network and browser are granted only when this node permission is on and the sandbox row has browser access on."
|
|
1055
|
+
: "Network is granted only when both this and the row's networkAllow are on. The row's browser access inherits through the same gate."}
|
|
986
1056
|
onChange={(checked) => patchConfig({ networkAccess: checked })}
|
|
987
1057
|
>
|
|
988
|
-
Network
|
|
1058
|
+
{sandboxBrowserOn && nodeUsesLocalIntelligence ? "Network + browser" : "Network"}
|
|
989
1059
|
</WorkflowCheckbox>
|
|
990
1060
|
</div>
|
|
991
1061
|
)}
|
|
@@ -1025,8 +1095,15 @@ export function OrchestrationNodeConfigPanel({
|
|
|
1025
1095
|
<WorkflowCheckbox checked={config.canWriteDraft === true} disabled={disabled} onChange={(checked) => patchConfig({ canWriteDraft: checked })}>
|
|
1026
1096
|
Write draft changes only
|
|
1027
1097
|
</WorkflowCheckbox>
|
|
1028
|
-
<WorkflowCheckbox
|
|
1029
|
-
|
|
1098
|
+
<WorkflowCheckbox
|
|
1099
|
+
checked={config.networkAccess === true}
|
|
1100
|
+
disabled={disabled}
|
|
1101
|
+
title={sandboxBrowserOn && nodeUsesLocalIntelligence
|
|
1102
|
+
? "This node gets browser access only when this permission and the sandbox row Browser access toggle are both on."
|
|
1103
|
+
: undefined}
|
|
1104
|
+
onChange={(checked) => patchConfig({ networkAccess: checked })}
|
|
1105
|
+
>
|
|
1106
|
+
{sandboxBrowserOn && nodeUsesLocalIntelligence ? "Allow network + browser access" : "Allow network access"}
|
|
1030
1107
|
</WorkflowCheckbox>
|
|
1031
1108
|
</div>
|
|
1032
1109
|
<KeyValueRows
|