@growthub/cli 0.14.0 → 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/helper/apply/route.js +99 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +1 -0
- 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 +84 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +107 -34
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +72 -15
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +264 -22
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +81 -10
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +179 -117
- 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/data-model/components/SidecarExpandView.jsx +37 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SwarmRunCockpit.jsx +625 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +150 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +136 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +61 -13
- 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 +224 -11
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +4 -0
- 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 +254 -4
- 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 +10 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +412 -1
- 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-helper.js +23 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +8 -6
- 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 +554 -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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper slash-command registry (SWARM_RUN_CONTRACT_V1, Phase 6).
|
|
3
|
+
*
|
|
4
|
+
* Pure module — no React, no fetch, no config writes. The HelperSidecar
|
|
5
|
+
* composer consumes this registry to render the "/" menu; unit tests assert
|
|
6
|
+
* its governance invariants.
|
|
7
|
+
*
|
|
8
|
+
* Governance rules encoded here:
|
|
9
|
+
* - read-only commands (mutates: false) may switch the sidecar view or
|
|
10
|
+
* seed a prompt — they never create proposals or patch config.
|
|
11
|
+
* - mutating commands (mutates: true) only ever SEED a helper proposal
|
|
12
|
+
* request (intent + prompt template). The proposal still travels the
|
|
13
|
+
* full governed chain: helper query → review → helper/apply → receipt.
|
|
14
|
+
* - no command executes sandbox-run directly and no command patches
|
|
15
|
+
* workspace config directly.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const HELPER_COMMANDS = [
|
|
19
|
+
{
|
|
20
|
+
name: "/goal",
|
|
21
|
+
label: "Goal",
|
|
22
|
+
description: "Set a verifiable goal for this helper session",
|
|
23
|
+
scope: "chat",
|
|
24
|
+
mutates: false,
|
|
25
|
+
promptTemplate: "Set a verifiable goal for this helper session:"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "/loop",
|
|
29
|
+
label: "Loop",
|
|
30
|
+
description: "Propose a governed recurring loop — reviewed before anything runs",
|
|
31
|
+
scope: "workspace",
|
|
32
|
+
mutates: true,
|
|
33
|
+
promptTemplate: "Propose a governed loop:"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "/workflows",
|
|
37
|
+
label: "Workflows",
|
|
38
|
+
description: "Open the background tasks list — read-only, no writes",
|
|
39
|
+
scope: "workspace",
|
|
40
|
+
mutates: false,
|
|
41
|
+
view: "swarm-list"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "/swarm",
|
|
45
|
+
label: "Swarm",
|
|
46
|
+
description: "Propose a governed agent swarm — you review and apply before any run",
|
|
47
|
+
scope: "swarm",
|
|
48
|
+
mutates: true,
|
|
49
|
+
intent: "swarm",
|
|
50
|
+
promptTemplate: "Propose a governed agent swarm:"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "/register-api",
|
|
54
|
+
label: "Register API",
|
|
55
|
+
description: "Draft an API Registry entry as a reviewable proposal",
|
|
56
|
+
scope: "workspace",
|
|
57
|
+
mutates: true,
|
|
58
|
+
intent: "register_api"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "/create-object",
|
|
62
|
+
label: "Create object",
|
|
63
|
+
description: "Translate a plain-language description into a new business object",
|
|
64
|
+
scope: "workspace",
|
|
65
|
+
mutates: true,
|
|
66
|
+
intent: "create_object"
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// The full behavioral surface a command may declare. Anything outside this
|
|
71
|
+
// list (execute hooks, patch fields, fetch targets…) is a governance
|
|
72
|
+
// violation — commands are a keyboard front-end to the existing pill
|
|
73
|
+
// intent system, never an action runner.
|
|
74
|
+
export const HELPER_COMMAND_ALLOWED_KEYS = [
|
|
75
|
+
"name",
|
|
76
|
+
"label",
|
|
77
|
+
"description",
|
|
78
|
+
"scope",
|
|
79
|
+
"mutates",
|
|
80
|
+
"promptTemplate",
|
|
81
|
+
"view",
|
|
82
|
+
"intent"
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pure governance validator for one command entry. Used by the unit suite
|
|
87
|
+
* against the live registry AND against forged entries (proving the
|
|
88
|
+
* invariant bites). Returns { ok, error }.
|
|
89
|
+
*/
|
|
90
|
+
export function isGovernedHelperCommand(cmd) {
|
|
91
|
+
if (!cmd || typeof cmd !== "object") return { ok: false, error: "command must be an object" };
|
|
92
|
+
if (typeof cmd.name !== "string" || !cmd.name.startsWith("/")) {
|
|
93
|
+
return { ok: false, error: "command name must start with /" };
|
|
94
|
+
}
|
|
95
|
+
if (typeof cmd.label !== "string" || !cmd.label) return { ok: false, error: `${cmd.name}: label required` };
|
|
96
|
+
if (typeof cmd.mutates !== "boolean") return { ok: false, error: `${cmd.name}: mutates must be declared` };
|
|
97
|
+
for (const key of Object.keys(cmd)) {
|
|
98
|
+
if (!HELPER_COMMAND_ALLOWED_KEYS.includes(key)) {
|
|
99
|
+
return { ok: false, error: `${cmd.name}: behavior key "${key}" is outside the governed surface` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (cmd.mutates) {
|
|
103
|
+
if (!cmd.intent && !cmd.promptTemplate) {
|
|
104
|
+
return { ok: false, error: `${cmd.name}: mutating commands must seed a governed proposal request` };
|
|
105
|
+
}
|
|
106
|
+
if (cmd.view) {
|
|
107
|
+
return { ok: false, error: `${cmd.name}: mutating commands must not switch views directly` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { ok: true, error: null };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Fuzzy-filter the registry against what the user typed after "/".
|
|
115
|
+
* Matches subsequences against the command name and label so "/wf" hits
|
|
116
|
+
* "/workflows" and "swm" hits "/swarm". Empty query returns everything.
|
|
117
|
+
*/
|
|
118
|
+
export function matchHelperCommands(query, commands = HELPER_COMMANDS) {
|
|
119
|
+
const text = String(query || "").trim().toLowerCase().replace(/^\//, "");
|
|
120
|
+
if (!text) return commands.slice();
|
|
121
|
+
const isSubsequence = (needle, haystack) => {
|
|
122
|
+
let i = 0;
|
|
123
|
+
for (const ch of haystack) {
|
|
124
|
+
if (ch === needle[i]) i += 1;
|
|
125
|
+
if (i >= needle.length) return true;
|
|
126
|
+
}
|
|
127
|
+
return needle.length === 0;
|
|
128
|
+
};
|
|
129
|
+
return commands.filter((cmd) => {
|
|
130
|
+
const name = cmd.name.toLowerCase().replace(/^\//, "");
|
|
131
|
+
const label = cmd.label.toLowerCase();
|
|
132
|
+
return name.includes(text) || label.includes(text)
|
|
133
|
+
|| isSubsequence(text, name) || isSubsequence(text, label);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Parse a composer value into a slash-menu state. The menu only engages
|
|
139
|
+
* when "/" is the FIRST character of the prompt — a slash mid-sentence
|
|
140
|
+
* (URLs, paths) never hijacks typing.
|
|
141
|
+
*/
|
|
142
|
+
export function parseSlashInput(value) {
|
|
143
|
+
const text = String(value || "");
|
|
144
|
+
if (!text.startsWith("/")) return { active: false, query: "", matches: [] };
|
|
145
|
+
// Once whitespace follows the command token the user is writing the
|
|
146
|
+
// body — keep the menu closed.
|
|
147
|
+
const token = text.slice(1);
|
|
148
|
+
if (/\s/.test(token)) return { active: false, query: "", matches: [] };
|
|
149
|
+
return { active: true, query: token, matches: matchHelperCommands(token) };
|
|
150
|
+
}
|
package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css
CHANGED
|
@@ -5435,7 +5435,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
|
|
|
5435
5435
|
min-height: 520px;
|
|
5436
5436
|
align-items: center;
|
|
5437
5437
|
justify-content: center;
|
|
5438
|
-
padding:
|
|
5438
|
+
padding: 96px 24px;
|
|
5439
5439
|
border: 0;
|
|
5440
5440
|
border-radius: 0;
|
|
5441
5441
|
background-color: #fff;
|
|
@@ -5443,6 +5443,12 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
|
|
|
5443
5443
|
background-size: 20px 20px;
|
|
5444
5444
|
box-shadow: none;
|
|
5445
5445
|
overflow: hidden;
|
|
5446
|
+
cursor: grab;
|
|
5447
|
+
touch-action: none;
|
|
5448
|
+
user-select: none;
|
|
5449
|
+
}
|
|
5450
|
+
.dm-workflow-orchestration .dm-orchestration-canvas:active {
|
|
5451
|
+
cursor: grabbing;
|
|
5446
5452
|
}
|
|
5447
5453
|
.dm-workflow-orchestration .dm-orchestration-canvas__badge {
|
|
5448
5454
|
top: 16px;
|
|
@@ -5498,8 +5504,11 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
|
|
|
5498
5504
|
display: flex;
|
|
5499
5505
|
flex-direction: column;
|
|
5500
5506
|
align-items: center;
|
|
5501
|
-
|
|
5507
|
+
padding: 96px 0;
|
|
5508
|
+
transform-origin: center center;
|
|
5502
5509
|
transition: transform .12s ease;
|
|
5510
|
+
will-change: transform;
|
|
5511
|
+
cursor: default;
|
|
5503
5512
|
}
|
|
5504
5513
|
.dm-workflow-orchestration .dm-orchestration-canvas__step {
|
|
5505
5514
|
width: 173px;
|
|
@@ -5737,6 +5746,58 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
|
|
|
5737
5746
|
padding: 7px 9px;
|
|
5738
5747
|
box-sizing: border-box;
|
|
5739
5748
|
}
|
|
5749
|
+
.dm-workflow-orchestration .dm-orchestration-config__field input[type="number"]::-webkit-outer-spin-button,
|
|
5750
|
+
.dm-workflow-orchestration .dm-orchestration-config__field input[type="number"]::-webkit-inner-spin-button {
|
|
5751
|
+
-webkit-appearance: none;
|
|
5752
|
+
margin: 0;
|
|
5753
|
+
}
|
|
5754
|
+
.dm-workflow-orchestration .dm-orchestration-config__field input[type="number"] {
|
|
5755
|
+
appearance: textfield;
|
|
5756
|
+
-moz-appearance: textfield;
|
|
5757
|
+
}
|
|
5758
|
+
.dm-workflow-orchestration .dm-workflow-check {
|
|
5759
|
+
width: fit-content;
|
|
5760
|
+
display: inline-flex;
|
|
5761
|
+
grid-template-columns: none;
|
|
5762
|
+
align-items: center;
|
|
5763
|
+
gap: 7px;
|
|
5764
|
+
min-height: 26px;
|
|
5765
|
+
cursor: pointer;
|
|
5766
|
+
}
|
|
5767
|
+
.dm-workflow-orchestration .dm-workflow-check input[type="checkbox"] {
|
|
5768
|
+
position: absolute;
|
|
5769
|
+
width: 1px;
|
|
5770
|
+
height: 1px;
|
|
5771
|
+
opacity: 0;
|
|
5772
|
+
pointer-events: none;
|
|
5773
|
+
}
|
|
5774
|
+
.dm-workflow-orchestration .dm-workflow-check__box {
|
|
5775
|
+
width: 16px;
|
|
5776
|
+
height: 16px;
|
|
5777
|
+
display: inline-grid;
|
|
5778
|
+
place-items: center;
|
|
5779
|
+
flex: 0 0 16px;
|
|
5780
|
+
border: 1px solid #d1d5db;
|
|
5781
|
+
border-radius: 4px;
|
|
5782
|
+
background: #f8fafc;
|
|
5783
|
+
color: #111827;
|
|
5784
|
+
box-sizing: border-box;
|
|
5785
|
+
}
|
|
5786
|
+
.dm-workflow-orchestration .dm-workflow-check:hover .dm-workflow-check__box {
|
|
5787
|
+
border-color: #9ca3af;
|
|
5788
|
+
background: #fff;
|
|
5789
|
+
}
|
|
5790
|
+
.dm-workflow-orchestration .dm-workflow-check input[type="checkbox"]:checked + .dm-workflow-check__box {
|
|
5791
|
+
border-color: #111827;
|
|
5792
|
+
background: #fff;
|
|
5793
|
+
}
|
|
5794
|
+
.dm-workflow-orchestration .dm-workflow-check input[type="checkbox"]:focus-visible + .dm-workflow-check__box {
|
|
5795
|
+
outline: 2px solid #d1d5db;
|
|
5796
|
+
outline-offset: 2px;
|
|
5797
|
+
}
|
|
5798
|
+
.dm-workflow-orchestration .dm-workflow-check input[type="checkbox"]:disabled + .dm-workflow-check__box {
|
|
5799
|
+
opacity: .55;
|
|
5800
|
+
}
|
|
5740
5801
|
.dm-workflow-orchestration .dm-orchestration-config__field input:focus,
|
|
5741
5802
|
.dm-workflow-orchestration .dm-orchestration-config__field textarea:focus,
|
|
5742
5803
|
.dm-workflow-orchestration .dm-orchestration-config__field select:focus {
|
|
@@ -6068,6 +6129,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
|
|
|
6068
6129
|
.dm-orch-modal-summary span { display: block; font-size: 10px; font-weight: 700; text-transform: uppercase; color: #6b7280; margin-bottom: 4px; }
|
|
6069
6130
|
.dm-orch-modal-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid #edf0f3; }
|
|
6070
6131
|
.dm-record-drawer-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 18px; border-bottom: 1px solid #edf0f3; }
|
|
6132
|
+
.dm-record-drawer-head > div:first-child { min-width: 0; flex: 1 1 auto; }
|
|
6071
6133
|
.dm-record-drawer-actions { display: inline-flex; align-items: center; gap: 8px; }
|
|
6072
6134
|
.dm-record-head-run { min-height: 30px; padding: 0 11px; }
|
|
6073
6135
|
.dm-record-drawer-actions { display: inline-flex; align-items: center; gap: 8px; }
|
|
@@ -6079,7 +6141,7 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
|
|
|
6079
6141
|
.dm-drawer-hidden-fields > span { color: #64748b; font-size: 12px; font-weight: 600; }
|
|
6080
6142
|
.dm-drawer-hidden-list { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
6081
6143
|
.dm-record-drawer-head p { margin: 0 0 3px; color: #94a3b8; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
|
|
6082
|
-
.dm-record-drawer-head h2 { margin: 0; color: #111827; font-size: 16px; font-weight: 650; }
|
|
6144
|
+
.dm-record-drawer-head h2 { margin: 0; color: #111827; font-size: 16px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
6083
6145
|
.dm-record-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; scrollbar-gutter: stable; padding: 0 0 28px; }
|
|
6084
6146
|
.dm-record-testbar { display: flex; align-items: center; gap: 8px; padding: 10px 18px; border-bottom: 1px solid #edf0f3; background: #fbfdff; }
|
|
6085
6147
|
.dm-record-testbar > span:last-child { min-width: 0; color: #64748b; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -6188,11 +6250,19 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
|
|
|
6188
6250
|
.dm-drawer-section.open .dm-drawer-section-toggle svg { transform: rotate(90deg); }
|
|
6189
6251
|
.dm-drawer-section-body { display: grid; gap: 10px; padding: 11px; }
|
|
6190
6252
|
.dm-sandbox-config { display: grid; gap: 8px; }
|
|
6253
|
+
.dm-sandbox-config > .dm-cockpit { width: 100%; margin: 12px 0 8px; box-sizing: border-box; }
|
|
6191
6254
|
.dm-radio-row { display: grid; gap: 8px; }
|
|
6192
6255
|
.dm-radio-row label, .dm-check-row { display: grid; grid-template-columns: 16px minmax(0,1fr); align-items: start; column-gap: 8px; color: #1f2937; font-size: 12px; line-height: 1.35; }
|
|
6256
|
+
.dm-check-row-compact { grid-template-columns: 16px max-content 22px; align-items: center; width: fit-content; max-width: 100%; }
|
|
6193
6257
|
.dm-radio-row input[type="radio"], .dm-check-row input[type="checkbox"] { width: 14px; height: 14px; margin: 1px 0 0; padding: 0; box-shadow: none; accent-color: #111827; }
|
|
6194
6258
|
.dm-radio-row span, .dm-check-row span { color: #1f2937; font-size: 12px; font-weight: 500; }
|
|
6195
6259
|
.dm-check-row { cursor: pointer; }
|
|
6260
|
+
.dm-check-row-compact > label { color: #1f2937; font-size: 12px; font-weight: 500; cursor: pointer; }
|
|
6261
|
+
.dm-help-wrap { position: relative; display: inline-flex; align-items: center; min-width: 0; }
|
|
6262
|
+
.dm-icon-help { width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 4px; background: transparent; color: #64748b; padding: 0; cursor: help; }
|
|
6263
|
+
.dm-icon-help:hover, .dm-icon-help:focus-visible { background: #f1f5f9; color: #334155; outline: none; }
|
|
6264
|
+
.dm-help-bubble { position: absolute; z-index: 80; left: 22px; top: 50%; transform: translateY(-50%); width: min(280px, calc(100vw - 48px)); display: none; border: 1px solid #cbd5e1; border-radius: 6px; background: #fff; box-shadow: 0 12px 30px rgba(15,23,42,.16); color: #334155; font-size: 11px; font-weight: 500; line-height: 1.4; padding: 8px 9px; }
|
|
6265
|
+
.dm-help-wrap:hover .dm-help-bubble, .dm-help-bubble.is-open { display: block; }
|
|
6196
6266
|
.dm-select { position: relative; width: 100%; min-width: 0; font-size: 11px; }
|
|
6197
6267
|
.dm-select-trigger { width: 100%; min-height: 32px; display: flex; align-items: center; justify-content: space-between; gap: 8px; border: 1px solid #cbd5e1; border-radius: 7px; background: #fff; color: #111827; box-shadow: 0 1px 2px rgba(15,23,42,.05); font: inherit; font-size: 11px; padding: 6px 10px; text-align: left; cursor: pointer; transition: border-color .12s, box-shadow .12s, background .12s; }
|
|
6198
6268
|
.dm-select-trigger:hover:not(:disabled) { border-color: #94a3b8; box-shadow: 0 2px 8px rgba(15,23,42,.08); }
|
|
@@ -8746,6 +8816,10 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
|
|
|
8746
8816
|
.dm-run-console__tree-dot[data-variant="fail"] { background: #dc2626; }
|
|
8747
8817
|
.dm-run-console__tree-dot[data-variant="active"] { background: #2563eb; }
|
|
8748
8818
|
.dm-run-console__tree-dot[data-variant="canceled"] { background: #9ca3af; }
|
|
8819
|
+
/* Pending (hollow) — the ONE sanctioned swarm-cockpit grammar addition:
|
|
8820
|
+
"declared but not started" reads as an outline in the SAME grey token the
|
|
8821
|
+
canceled/base dot already uses. No new color values. */
|
|
8822
|
+
.dm-run-console__tree-dot[data-variant="pending"] { background: transparent; border: 1px solid #9ca3af; }
|
|
8749
8823
|
.dm-run-console__tree-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
8750
8824
|
.dm-run-console__tree-meta { color: #6b7280; font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
8751
8825
|
.dm-run-console__tree-bar { display: block; width: 56px; height: 4px; background: #e5e7eb; border-radius: 999px; overflow: hidden; }
|
|
@@ -9114,3 +9188,62 @@ body.workspace-rail-collapsed .workspace-builder.workspace-lens-page,
|
|
|
9114
9188
|
.workspace-template-context-banner { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 6px; background: #eff6ff; border: 1px solid #bfdbfe; color: #1e40af; font-size: 12px; margin: 0 0 12px; }
|
|
9115
9189
|
.workspace-template-context-banner.is-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; }
|
|
9116
9190
|
.workspace-template-context-banner .workspace-template-context-link { color: inherit; text-decoration: underline; font-weight: 600; margin-left: auto; }
|
|
9191
|
+
|
|
9192
|
+
/* ---------------------------------------------------------------------------
|
|
9193
|
+
Governed Swarm Cockpit (SWARM_RUN_CONTRACT_V1) — structural layout ONLY.
|
|
9194
|
+
No new colors, borders, backgrounds, icons, or motion: every visual
|
|
9195
|
+
treatment comes from existing primitives composed in the JSX —
|
|
9196
|
+
dm-helper-toolcall(-row/-title/-chevron/-body/-json), dm-helper-stream,
|
|
9197
|
+
dm-helper-error, dm-run-console__hint, dm-run-console__tree-dot,
|
|
9198
|
+
dm-btn-ghost, dm-sidecar-header, dm-helper-pill-menu. The dm-swarm-*
|
|
9199
|
+
classes below carry layout, spacing, grid, overflow, and typography
|
|
9200
|
+
weight/size exclusively. */
|
|
9201
|
+
|
|
9202
|
+
/* Slash command menu — same dm-helper-pill-menu primitive, anchored above
|
|
9203
|
+
the composer textarea instead of below a pill. */
|
|
9204
|
+
.dm-helper-composer-input { position: relative; }
|
|
9205
|
+
.dm-helper-slash-menu { bottom: calc(100% + 6px); top: auto; left: 0; right: 0; }
|
|
9206
|
+
.dm-helper-slash-menu .dm-helper-pill-menu-item { justify-content: space-between; gap: 10px; }
|
|
9207
|
+
.dm-helper-slash-menu .dm-field-hint { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
9208
|
+
.dm-helper-slash-name { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
9209
|
+
|
|
9210
|
+
/* Cockpit body — scrolling list inside the existing sidecar body. */
|
|
9211
|
+
.dm-swarm-body { display: flex; flex-direction: column; min-height: 0; }
|
|
9212
|
+
.dm-swarm-cockpit { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 14px 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
9213
|
+
.dm-swarm-cockpit-list { display: flex; flex-direction: column; gap: 8px; }
|
|
9214
|
+
.dm-swarm-section-row { display: flex; align-items: center; justify-content: space-between; margin-top: 4px; }
|
|
9215
|
+
|
|
9216
|
+
/* Run card — surface chrome comes from dm-helper-toolcall in the JSX. */
|
|
9217
|
+
.dm-swarm-card { padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; }
|
|
9218
|
+
.dm-swarm-card-head { display: flex; align-items: center; gap: 8px; }
|
|
9219
|
+
.dm-swarm-card-title { flex: 1; }
|
|
9220
|
+
.dm-swarm-card-action { height: 24px; padding: 0 7px; }
|
|
9221
|
+
.dm-swarm-card-meta { display: flex; align-items: center; gap: 10px; }
|
|
9222
|
+
.dm-swarm-card-meta .dm-run-console__hint { font-size: 12px; }
|
|
9223
|
+
.dm-swarm-card-kind { font-weight: 600; }
|
|
9224
|
+
.dm-swarm-card-desc { font-size: 12px; padding: 7px 9px; }
|
|
9225
|
+
|
|
9226
|
+
/* Phase groups — dm-helper-toolcall card + dm-helper-toolcall-row head. */
|
|
9227
|
+
.dm-swarm-phases { display: flex; flex-direction: column; gap: 6px; margin-top: 2px; }
|
|
9228
|
+
.dm-swarm-phase { display: flex; flex-direction: column; }
|
|
9229
|
+
.dm-swarm-phase-head { grid-template-columns: 1fr auto; min-height: 30px; padding: 6px 10px; }
|
|
9230
|
+
.dm-swarm-dotstrip { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; padding: 0 10px 8px; }
|
|
9231
|
+
|
|
9232
|
+
/* Agent table — Agent | Tokens | Tools | Time, inside dm-helper-toolcall-body. */
|
|
9233
|
+
.dm-swarm-agent-table { display: flex; flex-direction: column; gap: 1px; }
|
|
9234
|
+
.dm-swarm-agent-row { display: grid; grid-template-columns: minmax(0, 1fr) 56px 44px 48px; align-items: center; gap: 6px; padding: 3px 4px; font: inherit; font-size: 12px; background: transparent; border: 0; cursor: pointer; text-align: left; }
|
|
9235
|
+
.dm-swarm-agent-header { cursor: default; }
|
|
9236
|
+
.dm-swarm-agent-name { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
9237
|
+
.dm-swarm-agent-cell { text-align: right; font-variant-numeric: tabular-nums; }
|
|
9238
|
+
|
|
9239
|
+
/* Transcript drill-in — existing tool-call JSON pre is the output frame. */
|
|
9240
|
+
.dm-swarm-transcript { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
|
|
9241
|
+
.dm-swarm-transcript-head { display: flex; align-items: center; justify-content: space-between; }
|
|
9242
|
+
.dm-swarm-transcript-expand { align-self: flex-end; margin-top: 4px; }
|
|
9243
|
+
|
|
9244
|
+
/* Expand view — full-width takeover within the same sidecar shell; the
|
|
9245
|
+
head reuses dm-sidecar-header chrome. */
|
|
9246
|
+
.dm-swarm-expand { display: flex; flex-direction: column; min-height: 0; flex: 1; }
|
|
9247
|
+
.dm-swarm-expand-head { justify-content: flex-start; gap: 8px; }
|
|
9248
|
+
.dm-swarm-expand-body { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 14px; }
|
|
9249
|
+
.dm-swarm-expand-body .dm-helper-toolcall-json { max-height: none; }
|
|
@@ -4,10 +4,10 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
6
6
|
import {
|
|
7
|
+
ArrowDown,
|
|
8
|
+
ArrowUp,
|
|
7
9
|
ArrowUpCircle,
|
|
8
10
|
Bot,
|
|
9
|
-
ChevronDown,
|
|
10
|
-
ChevronUp,
|
|
11
11
|
Code,
|
|
12
12
|
Filter,
|
|
13
13
|
FormInput,
|
|
@@ -135,6 +135,29 @@ function patchSandboxRowInConfig(workspaceConfig, objectId, rowIndex, fields) {
|
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
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
|
+
|
|
138
161
|
const WORKFLOW_ACTION_GROUPS = [
|
|
139
162
|
{
|
|
140
163
|
label: "Data",
|
|
@@ -243,6 +266,7 @@ function getNodeDeltaRecords(previousGraph, nextGraph) {
|
|
|
243
266
|
nodeId,
|
|
244
267
|
nodeType: String(node?.type || ""),
|
|
245
268
|
label: String(node?.label || node?.sandbox || nodeId),
|
|
269
|
+
sandboxRecordRef: config.sandboxRecordRef || null,
|
|
246
270
|
changeReason,
|
|
247
271
|
deltaTags,
|
|
248
272
|
requiresRetest: config.requiresRetest !== false,
|
|
@@ -497,8 +521,16 @@ export default function WorkflowSurface() {
|
|
|
497
521
|
|
|
498
522
|
const selectedNode = useMemo(() => {
|
|
499
523
|
if (!orchestrationGraph?.nodes || !selectedNodeId) return null;
|
|
500
|
-
|
|
501
|
-
|
|
524
|
+
const node = orchestrationGraph.nodes.find((n) => String(n.id) === selectedNodeId) || null;
|
|
525
|
+
if (!node) return null;
|
|
526
|
+
return {
|
|
527
|
+
...node,
|
|
528
|
+
config: {
|
|
529
|
+
...(node.config || {}),
|
|
530
|
+
sandboxRecordRef: nodeSandboxRecordRef(objectId, rowId, node.id)
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}, [orchestrationGraph, selectedNodeId, objectId, rowId]);
|
|
502
534
|
|
|
503
535
|
useEffect(() => {
|
|
504
536
|
if (graphUnset || graphBlankShell) {
|
|
@@ -521,7 +553,7 @@ export default function WorkflowSurface() {
|
|
|
521
553
|
}
|
|
522
554
|
|
|
523
555
|
function serializeCurrentGraph() {
|
|
524
|
-
return graphUnset ? "" : serializeOrchestrationGraph(orchestrationGraph);
|
|
556
|
+
return graphUnset ? "" : serializeOrchestrationGraph(withGraphSandboxRecordRefs(orchestrationGraph, objectId, rowId));
|
|
525
557
|
}
|
|
526
558
|
|
|
527
559
|
async function saveDraft(extraFields = {}) {
|
|
@@ -588,7 +620,8 @@ export default function WorkflowSurface() {
|
|
|
588
620
|
const nextVersion = Number.isFinite(currentVersion) ? String(currentVersion + 1) : "1";
|
|
589
621
|
const previousDeltas = Array.isArray(sandboxRow?.orchestrationDeltas) ? sandboxRow.orchestrationDeltas : [];
|
|
590
622
|
const previousPublishedGraph = parseOrchestrationGraph(sandboxRow?.[effectiveFieldName]);
|
|
591
|
-
const
|
|
623
|
+
const graphWithRefs = withGraphSandboxRecordRefs(orchestrationGraph, objectId, rowId);
|
|
624
|
+
const nodeDeltas = getNodeDeltaRecords(previousPublishedGraph, graphWithRefs);
|
|
592
625
|
const deltaTags = normalizeDeltaTags(nodeDeltas.flatMap((delta) => delta.deltaTags));
|
|
593
626
|
const changeReason = nodeDeltas.map((delta) => delta.changeReason).filter(Boolean).join("\n");
|
|
594
627
|
const next = patchSandboxRowInConfig(workspaceConfig, objectId, resolved.rowIndex, {
|
|
@@ -798,8 +831,12 @@ export default function WorkflowSurface() {
|
|
|
798
831
|
function handleNodeConfigChange(configPatch) {
|
|
799
832
|
if (!selectedNodeId) return;
|
|
800
833
|
const { __nodePatch, ...configOnly } = configPatch || {};
|
|
834
|
+
const recordRef = nodeSandboxRecordRef(objectId, rowId, selectedNodeId);
|
|
801
835
|
setOrchestrationGraph((g) => {
|
|
802
|
-
const updated = updateGraphNode(g, selectedNodeId,
|
|
836
|
+
const updated = updateGraphNode(g, selectedNodeId, {
|
|
837
|
+
...configOnly,
|
|
838
|
+
sandboxRecordRef: recordRef
|
|
839
|
+
});
|
|
803
840
|
if (!__nodePatch || typeof __nodePatch !== "object") return updated;
|
|
804
841
|
const parsed = parseOrchestrationGraph(updated) || updated;
|
|
805
842
|
return {
|
|
@@ -875,6 +912,7 @@ export default function WorkflowSurface() {
|
|
|
875
912
|
})
|
|
876
913
|
: null;
|
|
877
914
|
const isServerlessWorkflow = Boolean(serverlessState?.isServerless);
|
|
915
|
+
const showServerlessUpgrade = String(sandboxRow?.adapter || "").trim() !== "local-intelligence";
|
|
878
916
|
|
|
879
917
|
async function patchSandboxAndPersist(fields) {
|
|
880
918
|
if (resolved.rowIndex < 0 || !objectId || !workspaceConfig) return;
|
|
@@ -960,7 +998,7 @@ export default function WorkflowSurface() {
|
|
|
960
998
|
setAddTarget(null);
|
|
961
999
|
}}
|
|
962
1000
|
>
|
|
963
|
-
<
|
|
1001
|
+
<ArrowDown size={13} />
|
|
964
1002
|
</button>
|
|
965
1003
|
<button
|
|
966
1004
|
type="button"
|
|
@@ -974,9 +1012,9 @@ export default function WorkflowSurface() {
|
|
|
974
1012
|
setAddTarget(null);
|
|
975
1013
|
}}
|
|
976
1014
|
>
|
|
977
|
-
<
|
|
1015
|
+
<ArrowUp size={13} />
|
|
978
1016
|
</button>
|
|
979
|
-
{sandboxRow && (
|
|
1017
|
+
{sandboxRow && showServerlessUpgrade && (
|
|
980
1018
|
<button
|
|
981
1019
|
type="button"
|
|
982
1020
|
className={"dm-workflow-icon-btn dm-workflow-upgrade-btn" + (isServerlessWorkflow ? " is-serverless" : (upgradeState.showOnboarding ? " is-pulse" : ""))}
|
|
@@ -1014,7 +1052,13 @@ export default function WorkflowSurface() {
|
|
|
1014
1052
|
<Power size={13} /> {publishing ? "Publishing" : "Publish"}
|
|
1015
1053
|
</button>
|
|
1016
1054
|
)}
|
|
1017
|
-
<button
|
|
1055
|
+
<button
|
|
1056
|
+
type="button"
|
|
1057
|
+
className="dm-workflow-chip-btn"
|
|
1058
|
+
onClick={() => {
|
|
1059
|
+
if (sandboxRow) openTraceMode();
|
|
1060
|
+
}}
|
|
1061
|
+
>
|
|
1018
1062
|
<History size={13} /> See Runs
|
|
1019
1063
|
</button>
|
|
1020
1064
|
{sidecarMode === "trace" && (
|
|
@@ -1056,7 +1100,7 @@ export default function WorkflowSurface() {
|
|
|
1056
1100
|
|
|
1057
1101
|
{/* One-time serverless upgrade onboarding — shows only when the operator
|
|
1058
1102
|
has workflows but none are serverless, and hasn't dismissed it. */}
|
|
1059
|
-
{sandboxRow && !upgradeOpen && upgradeState.showOnboarding ? (
|
|
1103
|
+
{sandboxRow && showServerlessUpgrade && !upgradeOpen && upgradeState.showOnboarding ? (
|
|
1060
1104
|
<div className="workspace-template-context-banner dm-workflow-upgrade-nudge" role="note">
|
|
1061
1105
|
<div>
|
|
1062
1106
|
<strong>{upgradeState.headline}</strong>
|
|
@@ -1074,7 +1118,7 @@ export default function WorkflowSurface() {
|
|
|
1074
1118
|
{/* Serverless cockpit — same derivation + cockpit interface as the API
|
|
1075
1119
|
Registry and sandbox lanes. Toggles patch the sandbox row; deep config
|
|
1076
1120
|
(scheduler/adapter) routes to the object's Data Model drawer. */}
|
|
1077
|
-
{sandboxRow && upgradeOpen && serverlessState ? (
|
|
1121
|
+
{sandboxRow && showServerlessUpgrade && upgradeOpen && serverlessState ? (
|
|
1078
1122
|
<div className="dm-workflow-upgrade-panel">
|
|
1079
1123
|
<div className="dm-workflow-upgrade-panel-head">
|
|
1080
1124
|
<span className="dm-api-action-card-eyebrow">Persistence & scheduling</span>
|
|
@@ -1194,6 +1238,10 @@ export default function WorkflowSurface() {
|
|
|
1194
1238
|
</div>
|
|
1195
1239
|
<AgentSwarmPanel
|
|
1196
1240
|
graph={orchestrationGraph}
|
|
1241
|
+
objectId={objectId}
|
|
1242
|
+
rowName={rowId}
|
|
1243
|
+
sandboxRow={sandboxRow}
|
|
1244
|
+
onSandboxRowPatch={patchSandboxRuntimeFields}
|
|
1197
1245
|
disabled={false}
|
|
1198
1246
|
onGraphChange={(updater) => {
|
|
1199
1247
|
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.
|