@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
|
@@ -43,6 +43,17 @@ import {
|
|
|
43
43
|
} from "@/lib/workspace-helper-apply";
|
|
44
44
|
import { RESOLVER_PROPOSAL_TYPE, buildResolverProposal, validateResolverProposal } from "@/lib/workspace-resolver-proposal";
|
|
45
45
|
import { writeResolverProposalFile } from "@/lib/server-resolver-write";
|
|
46
|
+
import {
|
|
47
|
+
SWARM_PROPOSAL_TYPES,
|
|
48
|
+
SWARM_RUN_RESUME_PROPOSAL_TYPE,
|
|
49
|
+
SWARM_WORKFLOWS_OBJECT_ID,
|
|
50
|
+
validateSwarmRunProposal,
|
|
51
|
+
buildSandboxRowFromSwarmProposal,
|
|
52
|
+
deriveHelperWidgetCausationState,
|
|
53
|
+
upsertSwarmRunRow,
|
|
54
|
+
findSwarmRunRows,
|
|
55
|
+
summarizeSwarmRunProposal,
|
|
56
|
+
} from "@/lib/workspace-swarm-proposal";
|
|
46
57
|
|
|
47
58
|
const HELPER_APPLY_SOURCE_KEY = "helper:apply:receipts";
|
|
48
59
|
|
|
@@ -133,6 +144,65 @@ function normalizeApplyProposal(proposal, config, context = {}) {
|
|
|
133
144
|
return normalizeDataModelObjectProposal(normalizeResolverProposal(proposal, config), config, context.integrationId);
|
|
134
145
|
}
|
|
135
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Swarm lane (SWARM_RUN_CONTRACT_V1) — normalize a validated swarm proposal
|
|
149
|
+
* into the next workspace config plus an artifact target the sidecar cockpit
|
|
150
|
+
* can open. The model's intent payload is reduced through
|
|
151
|
+
* buildDefaultAgentSwarmGraph inside buildSandboxRowFromSwarmProposal — final
|
|
152
|
+
* graph JSON from the model is never trusted verbatim. Nothing executes here;
|
|
153
|
+
* runs stay behind POST /api/workspace/sandbox-run.
|
|
154
|
+
*
|
|
155
|
+
* Returns { ok, config, artifact, summary, error? }.
|
|
156
|
+
*/
|
|
157
|
+
function normalizeSwarmRunProposal(proposal, workspaceConfig) {
|
|
158
|
+
const validation = validateSwarmRunProposal(proposal);
|
|
159
|
+
if (!validation.ok) {
|
|
160
|
+
return { ok: false, config: workspaceConfig, artifact: null, summary: "", error: validation.error };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (proposal.type === SWARM_RUN_RESUME_PROPOSAL_TYPE) {
|
|
164
|
+
const name = String(proposal.payload?.name || "").trim();
|
|
165
|
+
const matches = findSwarmRunRows(workspaceConfig, { name });
|
|
166
|
+
if (matches.length === 0) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
config: workspaceConfig,
|
|
170
|
+
artifact: null,
|
|
171
|
+
summary: "",
|
|
172
|
+
error: `no governed swarm workflow named "${name}" to resume`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Resume is a governed pointer, not an execution: the receipt records the
|
|
176
|
+
// intent and the cockpit re-launches through sandbox-run.
|
|
177
|
+
return {
|
|
178
|
+
ok: true,
|
|
179
|
+
config: workspaceConfig,
|
|
180
|
+
artifact: { surface: "swarm-run", objectId: matches[0].objectId, name: matches[0].row.Name },
|
|
181
|
+
summary: summarizeSwarmRunProposal(proposal),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const helperState = deriveHelperWidgetCausationState(workspaceConfig);
|
|
186
|
+
if (!helperState.ready) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
config: workspaceConfig,
|
|
190
|
+
artifact: null,
|
|
191
|
+
summary: "",
|
|
192
|
+
error: helperState.guidance,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const row = buildSandboxRowFromSwarmProposal(workspaceConfig, proposal);
|
|
197
|
+
const nextConfig = upsertSwarmRunRow(workspaceConfig, row);
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
config: nextConfig,
|
|
201
|
+
artifact: { surface: "swarm-run", objectId: SWARM_WORKFLOWS_OBJECT_ID, name: row.Name },
|
|
202
|
+
summary: summarizeSwarmRunProposal(proposal),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
136
206
|
async function POST(request) {
|
|
137
207
|
let body;
|
|
138
208
|
try {
|
|
@@ -177,7 +247,10 @@ async function POST(request) {
|
|
|
177
247
|
normalizeApplyProposal(proposal, currentConfig, { integrationId: fallbackIntegrationId })
|
|
178
248
|
);
|
|
179
249
|
const resolverProposals = normalizedProposals.filter((p) => p?.type === RESOLVER_PROPOSAL_TYPE);
|
|
180
|
-
const
|
|
250
|
+
const swarmProposals = normalizedProposals.filter((p) => SWARM_PROPOSAL_TYPES.includes(p?.type));
|
|
251
|
+
const configProposals = normalizedProposals.filter(
|
|
252
|
+
(p) => p?.type !== RESOLVER_PROPOSAL_TYPE && !SWARM_PROPOSAL_TYPES.includes(p?.type)
|
|
253
|
+
);
|
|
181
254
|
|
|
182
255
|
for (const proposal of resolverProposals) {
|
|
183
256
|
const validation = validateResolverProposal(proposal);
|
|
@@ -201,6 +274,23 @@ async function POST(request) {
|
|
|
201
274
|
}
|
|
202
275
|
}
|
|
203
276
|
|
|
277
|
+
// Swarm lane — governed sandbox-environment rows in the EXISTING dataModel
|
|
278
|
+
// patch field. Apply creates/updates the row; execution stays behind
|
|
279
|
+
// POST /api/workspace/sandbox-run, launched explicitly from the cockpit.
|
|
280
|
+
for (const proposal of swarmProposals) {
|
|
281
|
+
const result = normalizeSwarmRunProposal(proposal, workingConfig);
|
|
282
|
+
if (!result.ok) {
|
|
283
|
+
skipped.push({ proposal, reason: result.error || "invalid swarm proposal" });
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
workingConfig = result.config;
|
|
287
|
+
applied.push({
|
|
288
|
+
...buildApplyReceipt({ ...proposal, affectedField: "dataModel" }, appliedAt, reviewedBy, sessionId),
|
|
289
|
+
artifact: result.artifact,
|
|
290
|
+
summary: result.summary,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
204
294
|
for (const proposal of configProposals) {
|
|
205
295
|
if (
|
|
206
296
|
!proposal ||
|
|
@@ -238,7 +328,11 @@ async function POST(request) {
|
|
|
238
328
|
// refreshes in the same atomic write as the proposed mutations).
|
|
239
329
|
// resolver.create writes a server file (affectedField "server-file"), so it
|
|
240
330
|
// must NOT contribute a field to the config PATCH — exclude it here.
|
|
241
|
-
|
|
331
|
+
// swarm.run.resume is a governed pointer (no config mutation) — exclude it
|
|
332
|
+
// alongside explain.object so it never forces a config write on its own.
|
|
333
|
+
const mutatingApplied = applied.filter(
|
|
334
|
+
(r) => r.type !== "explain.object" && r.type !== SWARM_RUN_RESUME_PROPOSAL_TYPE && r.affectedField !== "server-file"
|
|
335
|
+
);
|
|
242
336
|
|
|
243
337
|
// Upsert the thread row so audit history reflects this apply turn even
|
|
244
338
|
// when nothing mutated (all skipped / explain-only) and even when the
|
|
@@ -308,6 +402,9 @@ async function POST(request) {
|
|
|
308
402
|
"dataModel.row.add": "create_object",
|
|
309
403
|
"repair.binding": "repair",
|
|
310
404
|
"explain.object": "explain",
|
|
405
|
+
"swarm.run.propose": "swarm",
|
|
406
|
+
"swarm.workflow.save": "swarm",
|
|
407
|
+
"swarm.run.resume": "swarm",
|
|
311
408
|
};
|
|
312
409
|
const proposalIntent = firstProposal?.type ? TYPE_TO_INTENT_HINT[firstProposal.type] : null;
|
|
313
410
|
const safeIntent = existingRow.intent || proposalIntent || "explain";
|
|
@@ -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
|
|
@@ -338,6 +341,8 @@ async function runServerlessScheduler({
|
|
|
338
341
|
function buildRunResponse({
|
|
339
342
|
runId,
|
|
340
343
|
ranAt,
|
|
344
|
+
objectId,
|
|
345
|
+
name,
|
|
341
346
|
runLocality,
|
|
342
347
|
schedulerRegistryId,
|
|
343
348
|
runtime,
|
|
@@ -351,6 +356,7 @@ function buildRunResponse({
|
|
|
351
356
|
envRefsMissing,
|
|
352
357
|
networkAllow,
|
|
353
358
|
allowList,
|
|
359
|
+
browserAccess,
|
|
354
360
|
result,
|
|
355
361
|
timeoutMs,
|
|
356
362
|
row,
|
|
@@ -359,6 +365,10 @@ function buildRunResponse({
|
|
|
359
365
|
const base = {
|
|
360
366
|
runId,
|
|
361
367
|
ranAt,
|
|
368
|
+
// Identity travels with the persisted record so run-console consumers
|
|
369
|
+
// (lineage, swarm projection title) don't depend on the row context.
|
|
370
|
+
objectId: objectId ? String(objectId).trim() : undefined,
|
|
371
|
+
name: name ? String(name).trim() : undefined,
|
|
362
372
|
runLocality,
|
|
363
373
|
schedulerRegistryId: schedulerRegistryId ? String(schedulerRegistryId).trim() : null,
|
|
364
374
|
runtime,
|
|
@@ -378,6 +388,7 @@ function buildRunResponse({
|
|
|
378
388
|
envRefsMissing,
|
|
379
389
|
networkAllow,
|
|
380
390
|
allowList,
|
|
391
|
+
browserAccess,
|
|
381
392
|
adapterMeta: result.adapterMeta || null
|
|
382
393
|
};
|
|
383
394
|
if (row && (row.resolverTemplateId || row.connectorKind || row.executionLane)) {
|
|
@@ -439,14 +450,7 @@ async function GET(request) {
|
|
|
439
450
|
});
|
|
440
451
|
}
|
|
441
452
|
|
|
442
|
-
async function
|
|
443
|
-
let body;
|
|
444
|
-
try {
|
|
445
|
-
body = await request.json();
|
|
446
|
-
} catch {
|
|
447
|
-
return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
|
|
448
|
-
}
|
|
449
|
-
|
|
453
|
+
async function executeSandboxRun(body, { emit } = {}) {
|
|
450
454
|
const objectId = typeof body?.objectId === "string" ? body.objectId.trim() : "";
|
|
451
455
|
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
452
456
|
const useDraft = body?.useDraft === true;
|
|
@@ -498,7 +502,11 @@ async function POST(request) {
|
|
|
498
502
|
let adapterId = (typeof rowForRun.adapter === "string" && rowForRun.adapter.trim()) ? rowForRun.adapter.trim() : DEFAULT_SANDBOX_ADAPTER;
|
|
499
503
|
const agentHost = typeof rowForRun.agentHost === "string" ? rowForRun.agentHost.trim() : "";
|
|
500
504
|
const schedulerRegistryId = typeof rowForRun.schedulerRegistryId === "string" ? rowForRun.schedulerRegistryId.trim() : "";
|
|
501
|
-
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;
|
|
502
510
|
const allowList = parseSandboxAllowList(rowForRun.allowList);
|
|
503
511
|
const envRefSlugs = parseSandboxEnvRefs(rowForRun.envRefs);
|
|
504
512
|
const command = typeof rowForRun.command === "string" ? rowForRun.command : "";
|
|
@@ -577,10 +585,12 @@ async function POST(request) {
|
|
|
577
585
|
envRefsResolved,
|
|
578
586
|
networkAllow,
|
|
579
587
|
allowList,
|
|
588
|
+
browserAccess,
|
|
580
589
|
instructions,
|
|
581
590
|
command,
|
|
582
591
|
timeoutMs,
|
|
583
|
-
sandboxName: rowForRun.Name || name
|
|
592
|
+
sandboxName: rowForRun.Name || name,
|
|
593
|
+
onEvent: emit
|
|
584
594
|
}
|
|
585
595
|
});
|
|
586
596
|
if (graphResult !== null) {
|
|
@@ -607,6 +617,7 @@ async function POST(request) {
|
|
|
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 POST(request) {
|
|
|
640
651
|
timeoutMs,
|
|
641
652
|
networkAllow,
|
|
642
653
|
allowList,
|
|
654
|
+
browserAccess,
|
|
643
655
|
env,
|
|
644
656
|
envRefSlugs,
|
|
645
657
|
envRefsMissing,
|
|
@@ -665,6 +677,8 @@ async function POST(request) {
|
|
|
665
677
|
const response = buildRunResponse({
|
|
666
678
|
runId,
|
|
667
679
|
ranAt,
|
|
680
|
+
objectId,
|
|
681
|
+
name: rowForRun.Name || name,
|
|
668
682
|
runLocality,
|
|
669
683
|
schedulerRegistryId: runLocality === "serverless" ? schedulerRegistryId : null,
|
|
670
684
|
runtime,
|
|
@@ -678,6 +692,7 @@ async function POST(request) {
|
|
|
678
692
|
envRefsMissing,
|
|
679
693
|
networkAllow,
|
|
680
694
|
allowList,
|
|
695
|
+
browserAccess,
|
|
681
696
|
result,
|
|
682
697
|
timeoutMs,
|
|
683
698
|
row: rowForRun,
|
|
@@ -754,4 +769,63 @@ async function POST(request) {
|
|
|
754
769
|
});
|
|
755
770
|
}
|
|
756
771
|
|
|
772
|
+
async function POST(request) {
|
|
773
|
+
const accept = request.headers.get("accept") || "";
|
|
774
|
+
let body;
|
|
775
|
+
try {
|
|
776
|
+
body = await request.json();
|
|
777
|
+
} catch {
|
|
778
|
+
return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const wantsStream = body?.stream === true || accept.includes("application/x-ndjson");
|
|
782
|
+
if (!wantsStream) {
|
|
783
|
+
return executeSandboxRun(body);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const encoder = new TextEncoder();
|
|
787
|
+
const stream = new ReadableStream({
|
|
788
|
+
start(controller) {
|
|
789
|
+
const emit = (event) => {
|
|
790
|
+
controller.enqueue(encoder.encode(`${JSON.stringify(event)}\n`));
|
|
791
|
+
};
|
|
792
|
+
emit({
|
|
793
|
+
kind: "growthub-sandbox-run-delta-v1",
|
|
794
|
+
type: "sandbox-run.accepted",
|
|
795
|
+
emittedAt: new Date().toISOString(),
|
|
796
|
+
objectId: typeof body?.objectId === "string" ? body.objectId.trim() : "",
|
|
797
|
+
name: typeof body?.name === "string" ? body.name.trim() : ""
|
|
798
|
+
});
|
|
799
|
+
executeSandboxRun(body, { emit })
|
|
800
|
+
.then(async (response) => {
|
|
801
|
+
const finalPayload = await response.json().catch(() => ({ ok: false, error: "stream final payload unreadable" }));
|
|
802
|
+
emit({
|
|
803
|
+
kind: "growthub-sandbox-run-delta-v1",
|
|
804
|
+
type: "sandbox-run.final",
|
|
805
|
+
emittedAt: new Date().toISOString(),
|
|
806
|
+
status: response.status,
|
|
807
|
+
payload: finalPayload
|
|
808
|
+
});
|
|
809
|
+
})
|
|
810
|
+
.catch((error) => {
|
|
811
|
+
emit({
|
|
812
|
+
kind: "growthub-sandbox-run-delta-v1",
|
|
813
|
+
type: "sandbox-run.final",
|
|
814
|
+
emittedAt: new Date().toISOString(),
|
|
815
|
+
status: 500,
|
|
816
|
+
payload: { ok: false, error: error?.message || "sandbox run failed" }
|
|
817
|
+
});
|
|
818
|
+
})
|
|
819
|
+
.finally(() => controller.close());
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
return new Response(stream, {
|
|
824
|
+
headers: {
|
|
825
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
826
|
+
"cache-control": "no-store"
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
757
831
|
export { GET, POST };
|
|
@@ -14,7 +14,7 @@ const HELPER_AGENT_CHOICES = [
|
|
|
14
14
|
{ id: "gemini_local", label: "Gemini CLI (local)", body: "Uses Gemini CLI." },
|
|
15
15
|
{ id: "opencode_local", label: "OpenCode (local)", body: "Uses OpenCode on this machine." },
|
|
16
16
|
{ id: "pi_local", label: "Pi (local)", body: "Uses Pi on this machine." },
|
|
17
|
-
{ id: "
|
|
17
|
+
{ id: "qwen_local", label: "Qwen Code (local)", body: "Uses Qwen Code on this machine." },
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
const HELPER_EXECUTION_ADAPTERS = [
|
|
@@ -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>
|