@growthub/cli 0.14.0 → 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/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-run/route.js +70 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +61 -35
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +18 -4
- 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 +70 -85
- 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 +129 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +48 -9
- 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/orchestration-agent-swarm.js +246 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +6 -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/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-swarm-proposal.js +551 -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";
|
|
@@ -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 };
|
|
@@ -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 = [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
|
-
import { Plus, Trash2 } from "lucide-react";
|
|
4
|
+
import { Check, Plus, Trash2 } from "lucide-react";
|
|
5
5
|
import { HOST_AUTH_CATALOG } from "@/lib/sandbox-agent-host-catalog";
|
|
6
6
|
|
|
7
7
|
function getHostOptions() {
|
|
@@ -11,31 +11,63 @@ function getHostOptions() {
|
|
|
11
11
|
}));
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function
|
|
14
|
+
function nodeSandboxRecordRef(objectId, rowName, nodeId) {
|
|
15
|
+
return {
|
|
16
|
+
objectId: String(objectId || "").trim(),
|
|
17
|
+
rowName: String(rowName || "").trim(),
|
|
18
|
+
nodeId: String(nodeId || "").trim()
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function withRecordRef(patch, objectId, rowName, nodeId) {
|
|
23
|
+
return {
|
|
24
|
+
...patch,
|
|
25
|
+
sandboxRecordRef: nodeSandboxRecordRef(objectId, rowName, nodeId)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function WorkflowCheckbox({ checked, disabled, onChange, children, title }) {
|
|
30
|
+
return (
|
|
31
|
+
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline dm-workflow-check" title={title}>
|
|
32
|
+
<input
|
|
33
|
+
type="checkbox"
|
|
34
|
+
checked={checked}
|
|
35
|
+
disabled={disabled}
|
|
36
|
+
onChange={(event) => onChange?.(event.target.checked)}
|
|
37
|
+
/>
|
|
38
|
+
<span className="dm-workflow-check__box" aria-hidden="true">
|
|
39
|
+
{checked ? <Check size={13} strokeWidth={2.4} /> : null}
|
|
40
|
+
</span>
|
|
41
|
+
<span>{children}</span>
|
|
42
|
+
</label>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function patchOrchestrator(graph, patch, objectId, rowName) {
|
|
15
47
|
const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
16
48
|
return {
|
|
17
49
|
...graph,
|
|
18
50
|
nodes: nodes.map((node) =>
|
|
19
51
|
node?.type === "thinAdapter"
|
|
20
|
-
? { ...node, config: { ...(node.config || {}), ...patch } }
|
|
52
|
+
? { ...node, config: { ...(node.config || {}), ...withRecordRef(patch, objectId, rowName, node.id) } }
|
|
21
53
|
: node
|
|
22
54
|
)
|
|
23
55
|
};
|
|
24
56
|
}
|
|
25
57
|
|
|
26
|
-
function patchSynthesis(graph, patch) {
|
|
58
|
+
function patchSynthesis(graph, patch, objectId, rowName) {
|
|
27
59
|
const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
28
60
|
return {
|
|
29
61
|
...graph,
|
|
30
62
|
nodes: nodes.map((node) =>
|
|
31
63
|
node?.type === "tool-result"
|
|
32
|
-
? { ...node, config: { ...(node.config || {}), ...patch } }
|
|
64
|
+
? { ...node, config: { ...(node.config || {}), ...withRecordRef(patch, objectId, rowName, node.id) } }
|
|
33
65
|
: node
|
|
34
66
|
)
|
|
35
67
|
};
|
|
36
68
|
}
|
|
37
69
|
|
|
38
|
-
function patchSubagent(graph, nodeId, patch) {
|
|
70
|
+
function patchSubagent(graph, nodeId, patch, objectId, rowName) {
|
|
39
71
|
const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
40
72
|
return {
|
|
41
73
|
...graph,
|
|
@@ -44,7 +76,7 @@ function patchSubagent(graph, nodeId, patch) {
|
|
|
44
76
|
? {
|
|
45
77
|
...node,
|
|
46
78
|
label: patch.role != null ? String(patch.role) : node.label,
|
|
47
|
-
config: { ...(node.config || {}), ...patch }
|
|
79
|
+
config: { ...(node.config || {}), ...withRecordRef(patch, objectId, rowName, node.id) }
|
|
48
80
|
}
|
|
49
81
|
: node
|
|
50
82
|
)
|
|
@@ -107,7 +139,7 @@ function patchSwarmConfig(graph, patch) {
|
|
|
107
139
|
return { ...graph, swarm: { ...base, ...patch } };
|
|
108
140
|
}
|
|
109
141
|
|
|
110
|
-
export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
142
|
+
export function AgentSwarmPanel({ graph, objectId, rowName, onGraphChange, disabled }) {
|
|
111
143
|
const hostOptions = useMemo(getHostOptions, []);
|
|
112
144
|
if (!graph || typeof graph !== "object") return null;
|
|
113
145
|
|
|
@@ -133,7 +165,7 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
133
165
|
rows={3}
|
|
134
166
|
value={orchestrator?.config?.prompt || ""}
|
|
135
167
|
disabled={disabled || !orchestrator}
|
|
136
|
-
onChange={(e) => patchGraph((g) => patchOrchestrator(g, { prompt: e.target.value }))}
|
|
168
|
+
onChange={(e) => patchGraph((g) => patchOrchestrator(g, { prompt: e.target.value }, objectId, rowName))}
|
|
137
169
|
/>
|
|
138
170
|
</label>
|
|
139
171
|
</div>
|
|
@@ -163,7 +195,7 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
163
195
|
placeholder="Role"
|
|
164
196
|
value={cfg.role || node.label || ""}
|
|
165
197
|
disabled={disabled}
|
|
166
|
-
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { role: e.target.value }))}
|
|
198
|
+
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { role: e.target.value }, objectId, rowName))}
|
|
167
199
|
/>
|
|
168
200
|
<button
|
|
169
201
|
type="button"
|
|
@@ -181,7 +213,7 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
181
213
|
placeholder="One-sentence charter"
|
|
182
214
|
value={cfg.description || ""}
|
|
183
215
|
disabled={disabled}
|
|
184
|
-
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { description: e.target.value }))}
|
|
216
|
+
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { description: e.target.value }, objectId, rowName))}
|
|
185
217
|
/>
|
|
186
218
|
</label>
|
|
187
219
|
<label className="dm-orchestration-config__field">
|
|
@@ -190,7 +222,7 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
190
222
|
rows={2}
|
|
191
223
|
value={cfg.taskPrompt || ""}
|
|
192
224
|
disabled={disabled}
|
|
193
|
-
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { taskPrompt: e.target.value }))}
|
|
225
|
+
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { taskPrompt: e.target.value }, objectId, rowName))}
|
|
194
226
|
/>
|
|
195
227
|
</label>
|
|
196
228
|
<label className="dm-orchestration-config__field">
|
|
@@ -201,7 +233,7 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
201
233
|
disabled={disabled}
|
|
202
234
|
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, {
|
|
203
235
|
tools: e.target.value.split(",").map((t) => t.trim()).filter(Boolean)
|
|
204
|
-
}))}
|
|
236
|
+
}, objectId, rowName))}
|
|
205
237
|
/>
|
|
206
238
|
</label>
|
|
207
239
|
<label className="dm-orchestration-config__field">
|
|
@@ -212,7 +244,7 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
212
244
|
placeholder="0 = inherit"
|
|
213
245
|
value={cfg.maxTokens || 0}
|
|
214
246
|
disabled={disabled}
|
|
215
|
-
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { maxTokens: Math.max(0, Number(e.target.value) || 0) }))}
|
|
247
|
+
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { maxTokens: Math.max(0, Number(e.target.value) || 0) }, objectId, rowName))}
|
|
216
248
|
/>
|
|
217
249
|
</label>
|
|
218
250
|
<label className="dm-orchestration-config__field">
|
|
@@ -220,7 +252,7 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
220
252
|
<select
|
|
221
253
|
value={cfg.agentHost || ""}
|
|
222
254
|
disabled={disabled}
|
|
223
|
-
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { agentHost: e.target.value }))}
|
|
255
|
+
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { agentHost: e.target.value }, objectId, rowName))}
|
|
224
256
|
>
|
|
225
257
|
<option value="">Inherit</option>
|
|
226
258
|
{hostOptions.map((opt) => (
|
|
@@ -228,27 +260,21 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
228
260
|
))}
|
|
229
261
|
</select>
|
|
230
262
|
</label>
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
className="dm-orchestration-config__field dm-orchestration-config__field-inline"
|
|
263
|
+
<WorkflowCheckbox
|
|
264
|
+
checked={cfg.required !== false}
|
|
265
|
+
disabled={disabled}
|
|
266
|
+
onChange={(checked) => patchGraph((g) => patchSubagent(g, node.id, { required: checked }, objectId, rowName))}
|
|
267
|
+
>
|
|
268
|
+
Required
|
|
269
|
+
</WorkflowCheckbox>
|
|
270
|
+
<WorkflowCheckbox
|
|
271
|
+
checked={cfg.networkAccess === true}
|
|
272
|
+
disabled={disabled}
|
|
242
273
|
title="Network is granted only when both this and the row's networkAllow are on."
|
|
274
|
+
onChange={(checked) => patchGraph((g) => patchSubagent(g, node.id, { networkAccess: checked }, objectId, rowName))}
|
|
243
275
|
>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
checked={cfg.networkAccess === true}
|
|
247
|
-
disabled={disabled}
|
|
248
|
-
onChange={(e) => patchGraph((g) => patchSubagent(g, node.id, { networkAccess: e.target.checked }))}
|
|
249
|
-
/>
|
|
250
|
-
<span>Network</span>
|
|
251
|
-
</label>
|
|
276
|
+
Network
|
|
277
|
+
</WorkflowCheckbox>
|
|
252
278
|
</div>
|
|
253
279
|
);
|
|
254
280
|
})}
|
|
@@ -315,7 +341,7 @@ export function AgentSwarmPanel({ graph, onGraphChange, disabled }) {
|
|
|
315
341
|
rows={2}
|
|
316
342
|
value={synthesis?.config?.outcomePrompt || ""}
|
|
317
343
|
disabled={disabled}
|
|
318
|
-
onChange={(e) => patchGraph((g) => patchSynthesis(g, { outcomePrompt: e.target.value }))}
|
|
344
|
+
onChange={(e) => patchGraph((g) => patchSynthesis(g, { outcomePrompt: e.target.value }, objectId, rowName))}
|
|
319
345
|
/>
|
|
320
346
|
</label>
|
|
321
347
|
</div>
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
Trash2,
|
|
43
43
|
Type,
|
|
44
44
|
Unlock,
|
|
45
|
+
Upload,
|
|
45
46
|
Users,
|
|
46
47
|
X,
|
|
47
48
|
Zap,
|
|
@@ -1843,7 +1844,9 @@ function DataModelRecordDrawer({
|
|
|
1843
1844
|
<header className="dm-record-drawer-head">
|
|
1844
1845
|
<div>
|
|
1845
1846
|
<p>Record</p>
|
|
1846
|
-
<h2
|
|
1847
|
+
<h2 title={draft.Name || draft.integrationId || draft.id || `Row ${rowIndex + 1}`}>
|
|
1848
|
+
{draft.Name || draft.integrationId || draft.id || `Row ${rowIndex + 1}`}
|
|
1849
|
+
</h2>
|
|
1847
1850
|
</div>
|
|
1848
1851
|
<div className="dm-record-drawer-actions">
|
|
1849
1852
|
{isSandbox && sidecarMode !== "graph" && sidecarMode !== "trace" && (
|
|
@@ -1854,7 +1857,7 @@ function DataModelRecordDrawer({
|
|
|
1854
1857
|
onClick={runSandbox}
|
|
1855
1858
|
>
|
|
1856
1859
|
<Play size={13} aria-hidden />
|
|
1857
|
-
{sandboxRunning ? "Running…" : "
|
|
1860
|
+
{sandboxRunning ? "Running…" : "Execute"}
|
|
1858
1861
|
</button>
|
|
1859
1862
|
)}
|
|
1860
1863
|
{!isSandbox && sandboxToolFlow !== "draft" && (
|
|
@@ -2457,9 +2460,9 @@ function DataModelTableSurface({
|
|
|
2457
2460
|
const a = document.createElement("a");
|
|
2458
2461
|
a.href = url; a.download = `${table.source.replace(/\s+/g, "-").toLowerCase()}.csv`;
|
|
2459
2462
|
a.click(); URL.revokeObjectURL(url);
|
|
2460
|
-
}}
|
|
2463
|
+
}}><Download size={13} />Export CSV</button>
|
|
2461
2464
|
)}
|
|
2462
|
-
{table.mutable && <button type="button" className="dm-btn-ghost" onClick={() => setCsvOpen((open) => !open)}
|
|
2465
|
+
{table.mutable && <button type="button" className="dm-btn-ghost" onClick={() => setCsvOpen((open) => !open)}><Upload size={13} />Import CSV</button>}
|
|
2463
2466
|
{table.mutable && (
|
|
2464
2467
|
<button type="button" className="dm-btn-primary-sm" disabled={saving} onClick={() => onSave((config) => addTableRow(config, table))}>
|
|
2465
2468
|
<Plus size={13} />Add record
|
|
@@ -3544,6 +3547,17 @@ export default function DataModelShell() {
|
|
|
3544
3547
|
router.push(`/?dashboard=${encodeURIComponent(target.dashboardId)}`);
|
|
3545
3548
|
}
|
|
3546
3549
|
}}
|
|
3550
|
+
onOpenSwarmWorkflow={(target) => {
|
|
3551
|
+
const objectId = String(target?.objectId || "").trim();
|
|
3552
|
+
const rowName = String(target?.name || "").trim();
|
|
3553
|
+
if (!objectId || !rowName) return;
|
|
3554
|
+
const params = new URLSearchParams({
|
|
3555
|
+
object: objectId,
|
|
3556
|
+
row: rowName,
|
|
3557
|
+
field: "orchestrationGraph"
|
|
3558
|
+
});
|
|
3559
|
+
router.push(`/workflows?${params.toString()}`);
|
|
3560
|
+
}}
|
|
3547
3561
|
onApplied={(updatedConfig) => {
|
|
3548
3562
|
// Anchor the user on the most recently created/updated Data Model
|
|
3549
3563
|
// object so a helper-driven object.create lands on the surface
|