@growthub/cli 0.13.9 → 0.14.1
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/env-status/route.js +31 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +227 -5
- 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-run/route.js +70 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +17 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +6 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +61 -35
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +200 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +414 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +339 -77
- 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 +70 -85
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +2 -2
- 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 +229 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +224 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +2 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-agent-host.js +139 -4
- 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/api-registry-creation-flow.js +317 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-response-profile.js +207 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/creation-error-recovery.js +103 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +100 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +246 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +69 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +411 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +215 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-write.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-upgrade.js +89 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +11 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +8 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +30 -1
- 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-resolver-proposal.js +200 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-swarm-proposal.js +551 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workspace/env-status
|
|
3
|
+
*
|
|
4
|
+
* The honest, secret-safe auth-readiness signal the creation cockpit needs.
|
|
5
|
+
* Returns the referenced auth/env ref SLUGS that currently resolve to a value
|
|
6
|
+
* in the server runtime (process.env) — so the api-registry drawer can mark
|
|
7
|
+
* "auth configured" from real runtime truth instead of guessing. Never returns,
|
|
8
|
+
* logs, or hashes a value.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { NextResponse } from "next/server";
|
|
12
|
+
import { readWorkspaceConfig } from "@/lib/workspace-config";
|
|
13
|
+
import { computeConfiguredEnvRefs, listPersistenceAdapterReadiness } from "@/lib/env-status";
|
|
14
|
+
|
|
15
|
+
async function GET() {
|
|
16
|
+
let workspaceConfig = {};
|
|
17
|
+
try {
|
|
18
|
+
workspaceConfig = await readWorkspaceConfig();
|
|
19
|
+
} catch {
|
|
20
|
+
workspaceConfig = {};
|
|
21
|
+
}
|
|
22
|
+
const configuredEnvRefs = computeConfiguredEnvRefs(workspaceConfig, process.env);
|
|
23
|
+
const persistenceAdapters = listPersistenceAdapterReadiness(process.env);
|
|
24
|
+
return NextResponse.json({
|
|
25
|
+
kind: "growthub-env-status-v1",
|
|
26
|
+
configuredEnvRefs,
|
|
27
|
+
persistenceAdapters,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { GET };
|
|
@@ -41,9 +41,168 @@ import {
|
|
|
41
41
|
buildApplyReceipt,
|
|
42
42
|
upsertHelperThreadRow,
|
|
43
43
|
} from "@/lib/workspace-helper-apply";
|
|
44
|
+
import { RESOLVER_PROPOSAL_TYPE, buildResolverProposal, validateResolverProposal } from "@/lib/workspace-resolver-proposal";
|
|
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";
|
|
44
57
|
|
|
45
58
|
const HELPER_APPLY_SOURCE_KEY = "helper:apply:receipts";
|
|
46
59
|
|
|
60
|
+
function findRegistryRow(config, integrationId) {
|
|
61
|
+
const id = String(integrationId || "").trim();
|
|
62
|
+
if (!id) return null;
|
|
63
|
+
const objects = Array.isArray(config?.dataModel?.objects) ? config.dataModel.objects : [];
|
|
64
|
+
for (const object of objects) {
|
|
65
|
+
if (object?.objectType !== "api-registry") continue;
|
|
66
|
+
const row = (Array.isArray(object.rows) ? object.rows : []).find((candidate) =>
|
|
67
|
+
String(candidate?.integrationId || "").trim() === id
|
|
68
|
+
);
|
|
69
|
+
if (row) return row;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function findDataSourceForRegistry(config, integrationId) {
|
|
75
|
+
const id = String(integrationId || "").trim();
|
|
76
|
+
if (!id) return null;
|
|
77
|
+
const objects = Array.isArray(config?.dataModel?.objects) ? config.dataModel.objects : [];
|
|
78
|
+
for (const object of objects) {
|
|
79
|
+
if (object?.objectType !== "data-source") continue;
|
|
80
|
+
const row = (Array.isArray(object.rows) ? object.rows : []).find((candidate) =>
|
|
81
|
+
String(candidate?.registryId || "").trim() === id
|
|
82
|
+
);
|
|
83
|
+
if (row) return { object, row };
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function inferRootPathFromLastResponse(lastResponse) {
|
|
89
|
+
if (!lastResponse) return "";
|
|
90
|
+
let parsed = lastResponse;
|
|
91
|
+
if (typeof lastResponse === "string") {
|
|
92
|
+
try { parsed = JSON.parse(lastResponse); } catch { return ""; }
|
|
93
|
+
}
|
|
94
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "";
|
|
95
|
+
for (const key of ["records", "items", "data", "results", "capabilities"]) {
|
|
96
|
+
if (Array.isArray(parsed[key])) return key;
|
|
97
|
+
}
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeResolverProposal(proposal, config) {
|
|
102
|
+
if (proposal?.type !== RESOLVER_PROPOSAL_TYPE || String(proposal?.code || "").trim()) return proposal;
|
|
103
|
+
const integrationId = proposal?.payload?.integrationId;
|
|
104
|
+
const registryRow = findRegistryRow(config, integrationId);
|
|
105
|
+
if (!registryRow) return proposal;
|
|
106
|
+
const generated = buildResolverProposal({
|
|
107
|
+
integrationId: registryRow.integrationId,
|
|
108
|
+
baseUrl: registryRow.baseUrl,
|
|
109
|
+
endpoint: registryRow.endpoint,
|
|
110
|
+
method: registryRow.method,
|
|
111
|
+
authRef: registryRow.authRef,
|
|
112
|
+
rootPath: proposal?.payload?.rootPath || inferRootPathFromLastResponse(registryRow.lastResponse),
|
|
113
|
+
entityType: proposal?.payload?.entityType || registryRow.entityTypes || "records",
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
...generated,
|
|
117
|
+
rationale: proposal.rationale || generated.rationale,
|
|
118
|
+
confidence: proposal.confidence || generated.confidence,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeDataModelObjectProposal(proposal, config, fallbackIntegrationId = "") {
|
|
123
|
+
if (proposal?.type !== "dataModel.object.update" || proposal?.payload?.id) return proposal;
|
|
124
|
+
const integrationId = proposal?.payload?.registryId || proposal?.payload?.integrationId || fallbackIntegrationId;
|
|
125
|
+
const target = findDataSourceForRegistry(config, integrationId);
|
|
126
|
+
if (!target?.object) return proposal;
|
|
127
|
+
return {
|
|
128
|
+
...proposal,
|
|
129
|
+
payload: {
|
|
130
|
+
...proposal.payload,
|
|
131
|
+
id: target.object.id,
|
|
132
|
+
sourceId: target.object.sourceId || target.row?.sourceId || target.object.binding?.sourceId || "",
|
|
133
|
+
binding: {
|
|
134
|
+
...(target.object.binding || {}),
|
|
135
|
+
sourceStorage: target.object.binding?.sourceStorage || target.row?.sourceStorage || "workspace-source-records",
|
|
136
|
+
sourceId: target.object.binding?.sourceId || target.row?.sourceId || target.object.sourceId || "",
|
|
137
|
+
registryId: target.object.binding?.registryId || integrationId,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeApplyProposal(proposal, config, context = {}) {
|
|
144
|
+
return normalizeDataModelObjectProposal(normalizeResolverProposal(proposal, config), config, context.integrationId);
|
|
145
|
+
}
|
|
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
|
+
|
|
47
206
|
async function POST(request) {
|
|
48
207
|
let body;
|
|
49
208
|
try {
|
|
@@ -78,7 +237,61 @@ async function POST(request) {
|
|
|
78
237
|
const skipped = [];
|
|
79
238
|
let workingConfig = currentConfig;
|
|
80
239
|
|
|
81
|
-
|
|
240
|
+
// Resolver-file lane (AWaC: server file, NOT a config PATCH field). Handled
|
|
241
|
+
// separately so it never touches writeWorkspaceConfig and never widens the
|
|
242
|
+
// PATCH allowlist. Gated by filesystem/read-only; emits a receipt either way.
|
|
243
|
+
const fallbackIntegrationId = body.proposals
|
|
244
|
+
.map((proposal) => proposal?.payload?.integrationId || proposal?.payload?.registryId)
|
|
245
|
+
.find((value) => String(value || "").trim());
|
|
246
|
+
const normalizedProposals = body.proposals.map((proposal) =>
|
|
247
|
+
normalizeApplyProposal(proposal, currentConfig, { integrationId: fallbackIntegrationId })
|
|
248
|
+
);
|
|
249
|
+
const resolverProposals = normalizedProposals.filter((p) => p?.type === RESOLVER_PROPOSAL_TYPE);
|
|
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
|
+
);
|
|
254
|
+
|
|
255
|
+
for (const proposal of resolverProposals) {
|
|
256
|
+
const validation = validateResolverProposal(proposal);
|
|
257
|
+
if (!validation.ok) {
|
|
258
|
+
skipped.push({ proposal, reason: validation.error || "invalid resolver proposal" });
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const result = await writeResolverProposalFile(proposal);
|
|
263
|
+
applied.push({
|
|
264
|
+
...buildApplyReceipt(proposal, appliedAt, reviewedBy, sessionId),
|
|
265
|
+
resolverPath: result.path,
|
|
266
|
+
resolverFilename: result.filename,
|
|
267
|
+
});
|
|
268
|
+
} catch (err) {
|
|
269
|
+
if (err?.code === "WORKSPACE_PERSISTENCE_READ_ONLY") {
|
|
270
|
+
skipped.push({ proposal, reason: `read-only runtime — ${err.guidance || "resolver not written"}` });
|
|
271
|
+
} else {
|
|
272
|
+
skipped.push({ proposal, reason: err?.message || "resolver write failed" });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
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
|
+
|
|
294
|
+
for (const proposal of configProposals) {
|
|
82
295
|
if (
|
|
83
296
|
!proposal ||
|
|
84
297
|
typeof proposal.type !== "string" ||
|
|
@@ -113,7 +326,13 @@ async function POST(request) {
|
|
|
113
326
|
// Patch — collect every affected field from accepted proposals AND
|
|
114
327
|
// append the thread row update (so the user-visible Helper Threads object
|
|
115
328
|
// refreshes in the same atomic write as the proposed mutations).
|
|
116
|
-
|
|
329
|
+
// resolver.create writes a server file (affectedField "server-file"), so it
|
|
330
|
+
// must NOT contribute a field to the config PATCH — exclude it here.
|
|
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
|
+
);
|
|
117
336
|
|
|
118
337
|
// Upsert the thread row so audit history reflects this apply turn even
|
|
119
338
|
// when nothing mutated (all skipped / explain-only) and even when the
|
|
@@ -129,7 +348,7 @@ async function POST(request) {
|
|
|
129
348
|
try {
|
|
130
349
|
const existingRows = (workingConfig?.dataModel?.objects || []).find((o) => o?.id === "helper-threads")?.rows || [];
|
|
131
350
|
const existingRow = existingRows.find((r) => r?.id === threadId) || {};
|
|
132
|
-
const firstProposal =
|
|
351
|
+
const firstProposal = normalizedProposals?.[0];
|
|
133
352
|
const seedTitle = existingRow.title
|
|
134
353
|
|| (firstProposal?.rationale ? String(firstProposal.rationale).slice(0, 72) : "Helper thread");
|
|
135
354
|
|
|
@@ -183,6 +402,9 @@ async function POST(request) {
|
|
|
183
402
|
"dataModel.row.add": "create_object",
|
|
184
403
|
"repair.binding": "repair",
|
|
185
404
|
"explain.object": "explain",
|
|
405
|
+
"swarm.run.propose": "swarm",
|
|
406
|
+
"swarm.workflow.save": "swarm",
|
|
407
|
+
"swarm.run.resume": "swarm",
|
|
186
408
|
};
|
|
187
409
|
const proposalIntent = firstProposal?.type ? TYPE_TO_INTENT_HINT[firstProposal.type] : null;
|
|
188
410
|
const safeIntent = existingRow.intent || proposalIntent || "explain";
|
|
@@ -192,7 +414,7 @@ async function POST(request) {
|
|
|
192
414
|
intent: safeIntent,
|
|
193
415
|
prompt: existingRow.prompt || "",
|
|
194
416
|
summary: existingRow.summary || "",
|
|
195
|
-
proposals: existingRow.proposals ||
|
|
417
|
+
proposals: existingRow.proposals || normalizedProposals || [],
|
|
196
418
|
warnings: existingRow.warnings || [],
|
|
197
419
|
receipts: existingRow.receipts || null,
|
|
198
420
|
model: existingRow.model || "external-apply",
|
|
@@ -207,7 +429,7 @@ async function POST(request) {
|
|
|
207
429
|
affectedField: a.affectedField,
|
|
208
430
|
rationale: a.rationale,
|
|
209
431
|
confidence: a.confidence,
|
|
210
|
-
payload:
|
|
432
|
+
payload: normalizedProposals?.[idx]?.payload ?? null,
|
|
211
433
|
})),
|
|
212
434
|
lastSkipped: skipped.map((s) => ({
|
|
213
435
|
type: s.proposal?.type,
|
|
@@ -338,6 +338,8 @@ async function runServerlessScheduler({
|
|
|
338
338
|
function buildRunResponse({
|
|
339
339
|
runId,
|
|
340
340
|
ranAt,
|
|
341
|
+
objectId,
|
|
342
|
+
name,
|
|
341
343
|
runLocality,
|
|
342
344
|
schedulerRegistryId,
|
|
343
345
|
runtime,
|
|
@@ -359,6 +361,10 @@ function buildRunResponse({
|
|
|
359
361
|
const base = {
|
|
360
362
|
runId,
|
|
361
363
|
ranAt,
|
|
364
|
+
// Identity travels with the persisted record so run-console consumers
|
|
365
|
+
// (lineage, swarm projection title) don't depend on the row context.
|
|
366
|
+
objectId: objectId ? String(objectId).trim() : undefined,
|
|
367
|
+
name: name ? String(name).trim() : undefined,
|
|
362
368
|
runLocality,
|
|
363
369
|
schedulerRegistryId: schedulerRegistryId ? String(schedulerRegistryId).trim() : null,
|
|
364
370
|
runtime,
|
|
@@ -439,14 +445,7 @@ async function GET(request) {
|
|
|
439
445
|
});
|
|
440
446
|
}
|
|
441
447
|
|
|
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
|
-
|
|
448
|
+
async function executeSandboxRun(body, { emit } = {}) {
|
|
450
449
|
const objectId = typeof body?.objectId === "string" ? body.objectId.trim() : "";
|
|
451
450
|
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
452
451
|
const useDraft = body?.useDraft === true;
|
|
@@ -580,7 +579,8 @@ async function POST(request) {
|
|
|
580
579
|
instructions,
|
|
581
580
|
command,
|
|
582
581
|
timeoutMs,
|
|
583
|
-
sandboxName: rowForRun.Name || name
|
|
582
|
+
sandboxName: rowForRun.Name || name,
|
|
583
|
+
onEvent: emit
|
|
584
584
|
}
|
|
585
585
|
});
|
|
586
586
|
if (graphResult !== null) {
|
|
@@ -665,6 +665,8 @@ async function POST(request) {
|
|
|
665
665
|
const response = buildRunResponse({
|
|
666
666
|
runId,
|
|
667
667
|
ranAt,
|
|
668
|
+
objectId,
|
|
669
|
+
name: rowForRun.Name || name,
|
|
668
670
|
runLocality,
|
|
669
671
|
schedulerRegistryId: runLocality === "serverless" ? schedulerRegistryId : null,
|
|
670
672
|
runtime,
|
|
@@ -754,4 +756,63 @@ async function POST(request) {
|
|
|
754
756
|
});
|
|
755
757
|
}
|
|
756
758
|
|
|
759
|
+
async function POST(request) {
|
|
760
|
+
const accept = request.headers.get("accept") || "";
|
|
761
|
+
let body;
|
|
762
|
+
try {
|
|
763
|
+
body = await request.json();
|
|
764
|
+
} catch {
|
|
765
|
+
return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const wantsStream = body?.stream === true || accept.includes("application/x-ndjson");
|
|
769
|
+
if (!wantsStream) {
|
|
770
|
+
return executeSandboxRun(body);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const encoder = new TextEncoder();
|
|
774
|
+
const stream = new ReadableStream({
|
|
775
|
+
start(controller) {
|
|
776
|
+
const emit = (event) => {
|
|
777
|
+
controller.enqueue(encoder.encode(`${JSON.stringify(event)}\n`));
|
|
778
|
+
};
|
|
779
|
+
emit({
|
|
780
|
+
kind: "growthub-sandbox-run-delta-v1",
|
|
781
|
+
type: "sandbox-run.accepted",
|
|
782
|
+
emittedAt: new Date().toISOString(),
|
|
783
|
+
objectId: typeof body?.objectId === "string" ? body.objectId.trim() : "",
|
|
784
|
+
name: typeof body?.name === "string" ? body.name.trim() : ""
|
|
785
|
+
});
|
|
786
|
+
executeSandboxRun(body, { emit })
|
|
787
|
+
.then(async (response) => {
|
|
788
|
+
const finalPayload = await response.json().catch(() => ({ ok: false, error: "stream final payload unreadable" }));
|
|
789
|
+
emit({
|
|
790
|
+
kind: "growthub-sandbox-run-delta-v1",
|
|
791
|
+
type: "sandbox-run.final",
|
|
792
|
+
emittedAt: new Date().toISOString(),
|
|
793
|
+
status: response.status,
|
|
794
|
+
payload: finalPayload
|
|
795
|
+
});
|
|
796
|
+
})
|
|
797
|
+
.catch((error) => {
|
|
798
|
+
emit({
|
|
799
|
+
kind: "growthub-sandbox-run-delta-v1",
|
|
800
|
+
type: "sandbox-run.final",
|
|
801
|
+
emittedAt: new Date().toISOString(),
|
|
802
|
+
status: 500,
|
|
803
|
+
payload: { ok: false, error: error?.message || "sandbox run failed" }
|
|
804
|
+
});
|
|
805
|
+
})
|
|
806
|
+
.finally(() => controller.close());
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
return new Response(stream, {
|
|
811
|
+
headers: {
|
|
812
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
813
|
+
"cache-control": "no-store"
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
757
818
|
export { GET, POST };
|
|
@@ -51,6 +51,7 @@ export function WorkspaceActivationPanel({
|
|
|
51
51
|
workspaceSourceRecords,
|
|
52
52
|
metadataGraph,
|
|
53
53
|
onOpenHelper,
|
|
54
|
+
onStepAction,
|
|
54
55
|
compact = false,
|
|
55
56
|
showLenses = false,
|
|
56
57
|
}) {
|
|
@@ -136,7 +137,22 @@ export function WorkspaceActivationPanel({
|
|
|
136
137
|
<span>{step.hint}</span>
|
|
137
138
|
</p>
|
|
138
139
|
) : null}
|
|
139
|
-
{step.
|
|
140
|
+
{step.action && onStepAction ? (
|
|
141
|
+
<a
|
|
142
|
+
href={`#${step.action}`}
|
|
143
|
+
className={
|
|
144
|
+
"workspace-activation-step-cta"
|
|
145
|
+
+ (isNext ? " is-primary" : "")
|
|
146
|
+
}
|
|
147
|
+
onClick={(event) => {
|
|
148
|
+
event.preventDefault();
|
|
149
|
+
onStepAction(step);
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
<span>{step.cta || (step.status === "complete" ? "Review" : "Open")}</span>
|
|
153
|
+
<ArrowRight size={12} aria-hidden="true" />
|
|
154
|
+
</a>
|
|
155
|
+
) : step.href ? (
|
|
140
156
|
<Link
|
|
141
157
|
href={step.href}
|
|
142
158
|
className={
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
4
5
|
import { X } from "lucide-react";
|
|
5
6
|
|
|
6
7
|
const HELPER_SANDBOX_OBJECT_ID = "workspace-helper-sandbox";
|
|
@@ -13,7 +14,7 @@ const HELPER_AGENT_CHOICES = [
|
|
|
13
14
|
{ id: "gemini_local", label: "Gemini CLI (local)", body: "Uses Gemini CLI." },
|
|
14
15
|
{ id: "opencode_local", label: "OpenCode (local)", body: "Uses OpenCode on this machine." },
|
|
15
16
|
{ id: "pi_local", label: "Pi (local)", body: "Uses Pi on this machine." },
|
|
16
|
-
{ id: "
|
|
17
|
+
{ id: "qwen_local", label: "Qwen Code (local)", body: "Uses Qwen Code on this machine." },
|
|
17
18
|
];
|
|
18
19
|
|
|
19
20
|
const HELPER_EXECUTION_ADAPTERS = [
|
|
@@ -185,7 +186,9 @@ function WorkspaceHelperSetupModal({ workspaceConfig, open, onClose, onSaved })
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
return
|
|
189
|
+
if (typeof document === "undefined") return null;
|
|
190
|
+
|
|
191
|
+
return createPortal((
|
|
189
192
|
<div className="workspace-helper-setup-modal-backdrop" role="presentation">
|
|
190
193
|
<div className="workspace-helper-setup-modal" role="dialog" aria-modal="true" aria-label="Set up workspace helper">
|
|
191
194
|
<button type="button" className="workspace-helper-setup-modal-close" onClick={onClose} aria-label="Close setup">
|
|
@@ -342,7 +345,7 @@ function WorkspaceHelperSetupModal({ workspaceConfig, open, onClose, onSaved })
|
|
|
342
345
|
</div>
|
|
343
346
|
</div>
|
|
344
347
|
</div>
|
|
345
|
-
);
|
|
348
|
+
), document.body);
|
|
346
349
|
}
|
|
347
350
|
|
|
348
351
|
export {
|