@growthub/cli 0.14.3 → 0.14.5
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/resolvers/[integrationId]/route.js +157 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/env-status/route.js +5 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +33 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolvers/route.js +86 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +30 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryReviewModal.jsx +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/CeoCockpit.jsx +532 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +400 -188
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +36 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxOrchestrationEditorPanel.jsx +1 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +9 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +14 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +24 -19
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-agent-teams.js +211 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-bootstrap-console.js +325 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/ceo-cockpit-console.js +206 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +7 -82
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/resolver-constructor.js +217 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-registry.js +99 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/unified-resolver-registry.js +545 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-patch-policy.js +2 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +30 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +69 -0
- package/package.json +2 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryActionCard.jsx +0 -141
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolConfirmModal.jsx +0 -64
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxToolDraftPanel.jsx +0 -376
|
@@ -298,6 +298,75 @@ const DASHBOARD_TEMPLATES = [
|
|
|
298
298
|
dashboard: { name: "Blank", status: "draft" },
|
|
299
299
|
widgets: []
|
|
300
300
|
},
|
|
301
|
+
{
|
|
302
|
+
id: "ceo-daily-operating",
|
|
303
|
+
name: "CEO Daily Operating Dashboard",
|
|
304
|
+
description: "An executive operating surface for the CEO loop: today's focus, the loop scorecard, direct reports, blocked work, reusable Agent Team blueprints, and governance receipts. Companion to the CEO Cockpit (/ceo) — Agent Teams are the atomic config layer, the Fleet is the runtime, and the live next move lives in the cockpit.",
|
|
305
|
+
category: "reporting",
|
|
306
|
+
bestFor: ["Founders", "Operators", "Workspace orchestrators"],
|
|
307
|
+
tags: ["ceo", "operating", "executive", "daily", "agent-teams"],
|
|
308
|
+
preview: {
|
|
309
|
+
layout: "operating-grid",
|
|
310
|
+
summary: "Today's focus, loop scorecard, direct reports, blocked work, Agent Team blueprints, governance receipts, and ritual notes"
|
|
311
|
+
},
|
|
312
|
+
dashboard: { name: "CEO Daily Operating Dashboard", status: "draft" },
|
|
313
|
+
widgets: [
|
|
314
|
+
createWidget("rich-text", "Today's CEO Focus", { x: 0, y: 0, w: 8, h: 4 }, {
|
|
315
|
+
text: "Set this each morning: the one highest-leverage move today, what is blocked, what needs review, and what to launch next. The live, computed next move is always in the CEO Cockpit — open it with /ceo.",
|
|
316
|
+
binding: { mode: "manual", source: "Manual text", rows: [] }
|
|
317
|
+
}),
|
|
318
|
+
createWidget("chart", "CEO Loop Scorecard", { x: 8, y: 0, w: 4, h: 4 }, {
|
|
319
|
+
values: [5, 4, 3, 3, 1, 3],
|
|
320
|
+
binding: { mode: "manual", source: "Sample — Created · Ready · Launched · Completed · Blocked · Reviewed", rows: [] }
|
|
321
|
+
}),
|
|
322
|
+
createWidget("view", "Direct Reports / Swarm Fleet", { x: 0, y: 4, w: 7, h: 5 }, {
|
|
323
|
+
source: "Sample",
|
|
324
|
+
layout: "Table",
|
|
325
|
+
columns: ["Workflow", "State", "Readiness", "Last Outcome", "Next Move"],
|
|
326
|
+
rows: [
|
|
327
|
+
{ Workflow: "Example research swarm", State: "Completed", Readiness: "Ready", "Last Outcome": "Success", "Next Move": "Review output" },
|
|
328
|
+
{ Workflow: "Example outreach swarm", State: "Not run yet", Readiness: "Ready", "Last Outcome": "—", "Next Move": "Launch" },
|
|
329
|
+
{ Workflow: "Example audit swarm", State: "Blocked", Readiness: "Needs target", "Last Outcome": "—", "Next Move": "Set execution target" }
|
|
330
|
+
],
|
|
331
|
+
binding: { mode: "manual", source: "Sample rows — the live fleet is in the CEO Cockpit (/ceo)", rows: [] }
|
|
332
|
+
}),
|
|
333
|
+
createWidget("view", "Blocked or Failing Work", { x: 7, y: 4, w: 5, h: 5 }, {
|
|
334
|
+
source: "Sample",
|
|
335
|
+
layout: "Table",
|
|
336
|
+
columns: ["Workflow", "Blocker", "Severity", "Next Action"],
|
|
337
|
+
rows: [
|
|
338
|
+
{ Workflow: "Example audit swarm", Blocker: "No execution target", Severity: "High", "Next Action": "Set local adapter" },
|
|
339
|
+
{ Workflow: "Example billing swarm", Blocker: "Last run failed", Severity: "High", "Next Action": "Review + re-run" }
|
|
340
|
+
],
|
|
341
|
+
binding: { mode: "manual", source: "Sample rows — live blockers are in the CEO Cockpit (/ceo)", rows: [] }
|
|
342
|
+
}),
|
|
343
|
+
createWidget("view", "Agent Team Blueprints", { x: 0, y: 9, w: 6, h: 4 }, {
|
|
344
|
+
source: "Sample",
|
|
345
|
+
layout: "Table",
|
|
346
|
+
columns: ["Team", "Orchestrator", "Sub-agents", "Outcome"],
|
|
347
|
+
rows: [
|
|
348
|
+
{ Team: "Research & Synthesis", Orchestrator: "Research Lead", "Sub-agents": "Researcher; Analyst; Synthesizer", Outcome: "Cited brief + recommendation" },
|
|
349
|
+
{ Team: "Outreach", Orchestrator: "Campaign Lead", "Sub-agents": "Writer; Reviewer", Outcome: "Approved sequence" }
|
|
350
|
+
],
|
|
351
|
+
binding: { mode: "manual", source: "Sample blueprints — manage real teams in the Agent Swarm Teams object and launch via /ceo (config layer, not runtime)", rows: [] }
|
|
352
|
+
}),
|
|
353
|
+
createWidget("view", "Governance Receipts / Decisions", { x: 6, y: 9, w: 6, h: 4 }, {
|
|
354
|
+
source: "Sample",
|
|
355
|
+
layout: "Table",
|
|
356
|
+
columns: ["Time", "Lane", "Action", "Outcome", "Receipt"],
|
|
357
|
+
rows: [
|
|
358
|
+
{ Time: "09:14", Lane: "execution-proof", Action: "sandbox-run", Outcome: "completed", Receipt: "rcpt_sample_1" },
|
|
359
|
+
{ Time: "09:31", Lane: "governed-proposal", Action: "helper/apply", Outcome: "applied", Receipt: "rcpt_sample_2" },
|
|
360
|
+
{ Time: "09:42", Lane: "untrusted-direct", Action: "PATCH", Outcome: "blocked", Receipt: "rcpt_sample_3" }
|
|
361
|
+
],
|
|
362
|
+
binding: { mode: "manual", source: "Sample rows — live stream: GET /api/workspace/agent-outcomes", rows: [] }
|
|
363
|
+
}),
|
|
364
|
+
createWidget("rich-text", "Daily Ritual Notes", { x: 0, y: 13, w: 12, h: 3 }, {
|
|
365
|
+
text: "Morning: set today's focus. Midday: launch or review. Afternoon: unblock the failing one first. End of day: confirm the receipts. Define reusable Agent Teams, then launch them as governed swarms — runs land in the Fleet. Open /ceo for the live next move.",
|
|
366
|
+
binding: { mode: "manual", source: "Manual text", rows: [] }
|
|
367
|
+
})
|
|
368
|
+
]
|
|
369
|
+
},
|
|
301
370
|
{
|
|
302
371
|
id: "client-portal",
|
|
303
372
|
name: "Client Portal",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growthub/cli",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.5",
|
|
4
4
|
"description": "CLI control plane for Growthub Local and Agent Workspace as Code: export, fork, inspect, operate, sync, and optionally activate governed AI workspaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@aws-sdk/client-s3": "^3.888.0",
|
|
45
45
|
"@clack/prompts": "^0.10.0",
|
|
46
|
-
"@growthub/api-contract": "1.5.
|
|
46
|
+
"@growthub/api-contract": "1.5.1",
|
|
47
47
|
"ajv": "^8.18.0",
|
|
48
48
|
"ajv-formats": "^3.0.1",
|
|
49
49
|
"better-auth": "1.4.18",
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Check, ExternalLink, Play, Terminal, X } from "lucide-react";
|
|
4
|
-
import { getApiRegistrySandboxToolState } from "@/lib/orchestration-graph";
|
|
5
|
-
|
|
6
|
-
export function ApiRegistryActionCard({
|
|
7
|
-
registryRow,
|
|
8
|
-
workspaceConfig,
|
|
9
|
-
disabled,
|
|
10
|
-
onCreateSandboxTool,
|
|
11
|
-
onOpenSandboxTool,
|
|
12
|
-
onRunSandboxTool,
|
|
13
|
-
onTestConnection,
|
|
14
|
-
testing,
|
|
15
|
-
sandboxRunning
|
|
16
|
-
}) {
|
|
17
|
-
const state = getApiRegistrySandboxToolState(registryRow, workspaceConfig);
|
|
18
|
-
|
|
19
|
-
if (state.kind === "incomplete") {
|
|
20
|
-
const checklist = state.checklist || [];
|
|
21
|
-
return (
|
|
22
|
-
<section className="dm-api-action-card dm-api-action-card-muted" aria-label="Complete API setup">
|
|
23
|
-
<div className="dm-api-action-card-body">
|
|
24
|
-
<p className="dm-api-action-card-eyebrow">API Registry</p>
|
|
25
|
-
<h3>Complete API setup</h3>
|
|
26
|
-
<p>
|
|
27
|
-
This API needs a registry ID, base URL, endpoint, method, and auth reference before it can become a sandbox tool.
|
|
28
|
-
</p>
|
|
29
|
-
<ul className="dm-api-action-checklist">
|
|
30
|
-
{checklist.map((item) => (
|
|
31
|
-
<li key={item.field} className={item.ok ? "is-done" : "is-pending"}>
|
|
32
|
-
{item.ok ? <Check size={14} aria-hidden="true" /> : <X size={14} aria-hidden="true" />}
|
|
33
|
-
<span>{item.field}</span>
|
|
34
|
-
</li>
|
|
35
|
-
))}
|
|
36
|
-
</ul>
|
|
37
|
-
</div>
|
|
38
|
-
</section>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (state.kind === "untested") {
|
|
43
|
-
return (
|
|
44
|
-
<section className="dm-api-action-card dm-api-action-card-muted" aria-label="Test API first">
|
|
45
|
-
<div className="dm-api-action-card-body">
|
|
46
|
-
<p className="dm-api-action-card-eyebrow">Sandbox tool</p>
|
|
47
|
-
<h3>Test this API first</h3>
|
|
48
|
-
<p>{state.message}</p>
|
|
49
|
-
</div>
|
|
50
|
-
<button
|
|
51
|
-
type="button"
|
|
52
|
-
className="dm-btn-primary-sm dm-api-action-card-cta"
|
|
53
|
-
disabled={disabled || testing}
|
|
54
|
-
onClick={onTestConnection}
|
|
55
|
-
>
|
|
56
|
-
{testing ? "Testing…" : "Test connection"}
|
|
57
|
-
</button>
|
|
58
|
-
</section>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (state.kind === "failed") {
|
|
63
|
-
return (
|
|
64
|
-
<section className="dm-api-action-card dm-api-action-card-muted" aria-label="API test failed">
|
|
65
|
-
<div className="dm-api-action-card-body">
|
|
66
|
-
<p className="dm-api-action-card-eyebrow">Connection</p>
|
|
67
|
-
<h3>API test failed</h3>
|
|
68
|
-
<p>{state.message}</p>
|
|
69
|
-
</div>
|
|
70
|
-
<button
|
|
71
|
-
type="button"
|
|
72
|
-
className="dm-btn-outline dm-api-action-card-cta"
|
|
73
|
-
disabled={disabled || testing}
|
|
74
|
-
onClick={onTestConnection}
|
|
75
|
-
>
|
|
76
|
-
{testing ? "Testing…" : "Retest"}
|
|
77
|
-
</button>
|
|
78
|
-
</section>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (state.kind === "existing") {
|
|
83
|
-
const toolName = String(state.row?.Name || "").trim();
|
|
84
|
-
return (
|
|
85
|
-
<section className="dm-api-action-card" aria-label="Sandbox tool ready">
|
|
86
|
-
<div className="dm-api-action-card-icon" aria-hidden="true">
|
|
87
|
-
<Terminal size={18} />
|
|
88
|
-
</div>
|
|
89
|
-
<div className="dm-api-action-card-body">
|
|
90
|
-
<p className="dm-api-action-card-eyebrow">Sandbox tool</p>
|
|
91
|
-
<h3>Sandbox tool ready</h3>
|
|
92
|
-
<p>{toolName} — governed row linked to this API Registry entry.</p>
|
|
93
|
-
</div>
|
|
94
|
-
<div className="dm-api-action-card-actions">
|
|
95
|
-
<button
|
|
96
|
-
type="button"
|
|
97
|
-
className="dm-btn-outline dm-api-action-card-cta"
|
|
98
|
-
disabled={disabled || !toolName}
|
|
99
|
-
onClick={() => onOpenSandboxTool?.({ name: toolName })}
|
|
100
|
-
>
|
|
101
|
-
<ExternalLink size={14} aria-hidden="true" />
|
|
102
|
-
Open sandbox tool
|
|
103
|
-
</button>
|
|
104
|
-
<button
|
|
105
|
-
type="button"
|
|
106
|
-
className="dm-btn-primary-sm dm-api-action-card-cta"
|
|
107
|
-
disabled={disabled || sandboxRunning || !toolName}
|
|
108
|
-
onClick={() => onRunSandboxTool?.({ name: toolName })}
|
|
109
|
-
>
|
|
110
|
-
<Play size={14} aria-hidden="true" />
|
|
111
|
-
{sandboxRunning ? "Running…" : "Run sandbox"}
|
|
112
|
-
</button>
|
|
113
|
-
</div>
|
|
114
|
-
</section>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<section className="dm-api-action-card" aria-label="Create sandbox tool">
|
|
120
|
-
<div className="dm-api-action-card-icon" aria-hidden="true">
|
|
121
|
-
<Terminal size={18} />
|
|
122
|
-
</div>
|
|
123
|
-
<div className="dm-api-action-card-body">
|
|
124
|
-
<p className="dm-api-action-card-eyebrow">API connected</p>
|
|
125
|
-
<h3>Create sandbox tool</h3>
|
|
126
|
-
<p>
|
|
127
|
-
This API is connected. Turn it into a sandbox tool that agents can run safely from this workspace.
|
|
128
|
-
</p>
|
|
129
|
-
<p className="dm-api-action-card-note">No secrets are stored. Nothing runs until you test the sandbox.</p>
|
|
130
|
-
</div>
|
|
131
|
-
<button
|
|
132
|
-
type="button"
|
|
133
|
-
className="dm-btn-primary-sm dm-api-action-card-cta"
|
|
134
|
-
disabled={disabled}
|
|
135
|
-
onClick={onCreateSandboxTool}
|
|
136
|
-
>
|
|
137
|
-
Create sandbox tool
|
|
138
|
-
</button>
|
|
139
|
-
</section>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { X } from "lucide-react";
|
|
4
|
-
import { summarizeOrchestrationGraph } from "@/lib/orchestration-graph";
|
|
5
|
-
|
|
6
|
-
export function SandboxToolConfirmModal({
|
|
7
|
-
open,
|
|
8
|
-
toolName,
|
|
9
|
-
authRef,
|
|
10
|
-
orchestrationGraph,
|
|
11
|
-
onConfirm,
|
|
12
|
-
onCancel,
|
|
13
|
-
creating
|
|
14
|
-
}) {
|
|
15
|
-
if (!open) return null;
|
|
16
|
-
|
|
17
|
-
const summary = summarizeOrchestrationGraph(orchestrationGraph);
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div className="dm-orchestration-confirm dm-orchestration-confirm__backdrop" onClick={onCancel} role="presentation">
|
|
21
|
-
<section
|
|
22
|
-
className="dm-orchestration-confirm__dialog"
|
|
23
|
-
role="dialog"
|
|
24
|
-
aria-modal="true"
|
|
25
|
-
aria-labelledby="sandbox-tool-confirm-title"
|
|
26
|
-
onClick={(event) => event.stopPropagation()}
|
|
27
|
-
>
|
|
28
|
-
<header className="dm-orchestration-confirm__head">
|
|
29
|
-
<div>
|
|
30
|
-
<p>Confirm</p>
|
|
31
|
-
<h2 id="sandbox-tool-confirm-title">Create sandbox tool?</h2>
|
|
32
|
-
</div>
|
|
33
|
-
<button type="button" className="dm-sidebar-close" onClick={onCancel} aria-label="Close">
|
|
34
|
-
<X size={16} />
|
|
35
|
-
</button>
|
|
36
|
-
</header>
|
|
37
|
-
<div className="dm-orchestration-confirm__body">
|
|
38
|
-
<p>This creates one Sandbox Environment row from the tested API Registry record.</p>
|
|
39
|
-
<ul className="dm-orchestration-confirm__list">
|
|
40
|
-
<li>Saves orchestrationGraph on the sandbox row</li>
|
|
41
|
-
<li>Stores <code>{authRef || "authRef"}</code> only — no secrets</li>
|
|
42
|
-
<li>Does not store secrets</li>
|
|
43
|
-
<li>Does not create widgets</li>
|
|
44
|
-
<li>Does not change dashboards</li>
|
|
45
|
-
<li>Does not change canvas</li>
|
|
46
|
-
<li>Does not run until you click Run sandbox</li>
|
|
47
|
-
</ul>
|
|
48
|
-
<p className="dm-orchestration-confirm__summary">
|
|
49
|
-
<span>Run plan</span>
|
|
50
|
-
{summary}
|
|
51
|
-
</p>
|
|
52
|
-
</div>
|
|
53
|
-
<footer className="dm-orchestration-confirm__foot">
|
|
54
|
-
<button type="button" className="dm-btn-outline" disabled={creating} onClick={onCancel}>
|
|
55
|
-
Cancel
|
|
56
|
-
</button>
|
|
57
|
-
<button type="button" className="dm-btn-primary-sm" disabled={creating} onClick={onConfirm}>
|
|
58
|
-
{creating ? "Creating…" : "Create tool"}
|
|
59
|
-
</button>
|
|
60
|
-
</footer>
|
|
61
|
-
</section>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useMemo, useState } from "react";
|
|
4
|
-
import { ArrowLeft } from "lucide-react";
|
|
5
|
-
import {
|
|
6
|
-
addCanonicalNodeToGraph,
|
|
7
|
-
buildBlankOrchestrationGraphShell,
|
|
8
|
-
buildDefaultOrchestrationGraphFromRegistry,
|
|
9
|
-
getNextCanonicalNodeId,
|
|
10
|
-
isApiRegistryTestSuccessful,
|
|
11
|
-
getOrchestrationGraphUiState,
|
|
12
|
-
parseOrchestrationGraph,
|
|
13
|
-
serializeOrchestrationGraph,
|
|
14
|
-
updateGraphNode,
|
|
15
|
-
validateOrchestrationGraph
|
|
16
|
-
} from "@/lib/orchestration-graph";
|
|
17
|
-
import { resolveConnectorAction } from "@/lib/orchestration-sidecar-routing";
|
|
18
|
-
import { OrchestrationGraphCanvas } from "./OrchestrationGraphCanvas.jsx";
|
|
19
|
-
import { OrchestrationGraphEmptyCanvas } from "./OrchestrationGraphEmptyCanvas.jsx";
|
|
20
|
-
import { OrchestrationNodeConfigPanel } from "./OrchestrationNodeConfigPanel.jsx";
|
|
21
|
-
|
|
22
|
-
export function SandboxToolDraftPanel({
|
|
23
|
-
registryRow,
|
|
24
|
-
draftOptions,
|
|
25
|
-
onDraftChange,
|
|
26
|
-
onRequestConfirm,
|
|
27
|
-
onCancel,
|
|
28
|
-
disabled
|
|
29
|
-
}) {
|
|
30
|
-
const integrationId = String(registryRow?.integrationId || "").trim();
|
|
31
|
-
const registryName = String(registryRow?.Name || integrationId).trim();
|
|
32
|
-
const defaultName = registryRow?.Name
|
|
33
|
-
? `${String(registryRow.Name).trim()} Tool`
|
|
34
|
-
: `${integrationId} Tool`;
|
|
35
|
-
|
|
36
|
-
const [name, setName] = useState(draftOptions?.name || defaultName);
|
|
37
|
-
const [description, setDescription] = useState(draftOptions?.description || String(registryRow?.description || "").trim());
|
|
38
|
-
const [runLocality, setRunLocality] = useState(draftOptions?.runLocality || "local");
|
|
39
|
-
const [adapter, setAdapter] = useState(draftOptions?.adapter || "local-process");
|
|
40
|
-
const [authRef, setAuthRef] = useState(draftOptions?.authRef || String(registryRow?.authRef || integrationId).trim());
|
|
41
|
-
const [envRefs, setEnvRefs] = useState(draftOptions?.envRefs || "");
|
|
42
|
-
const [networkAllow, setNetworkAllow] = useState(Boolean(draftOptions?.networkAllow));
|
|
43
|
-
const [timeoutMs, setTimeoutMs] = useState(String(draftOptions?.timeoutMs || "30000"));
|
|
44
|
-
const [rootPath, setRootPath] = useState(draftOptions?.rootPath || "data");
|
|
45
|
-
const [instructions, setInstructions] = useState(draftOptions?.instructions || "");
|
|
46
|
-
const [agentHost, setAgentHost] = useState(draftOptions?.agentHost || "");
|
|
47
|
-
const [schedulerRegistryId, setSchedulerRegistryId] = useState(
|
|
48
|
-
draftOptions?.schedulerRegistryId || (draftOptions?.runLocality === "serverless" ? integrationId : "")
|
|
49
|
-
);
|
|
50
|
-
const [selectedNodeId, setSelectedNodeId] = useState("input");
|
|
51
|
-
const [configTab, setConfigTab] = useState("node");
|
|
52
|
-
const [graphError, setGraphError] = useState("");
|
|
53
|
-
const [orchestrationGraph, setOrchestrationGraph] = useState(() => {
|
|
54
|
-
if (draftOptions?.orchestrationGraph) {
|
|
55
|
-
return parseOrchestrationGraph(draftOptions.orchestrationGraph);
|
|
56
|
-
}
|
|
57
|
-
return null;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const registryKey = `${integrationId}:${String(registryRow?.endpoint || "")}:${String(registryRow?.method || "")}`;
|
|
61
|
-
const graphUiState = getOrchestrationGraphUiState(orchestrationGraph);
|
|
62
|
-
const graphUnset = graphUiState === "unset";
|
|
63
|
-
const graphBlankShell = graphUiState === "blank-shell";
|
|
64
|
-
const nextNodeId = useMemo(
|
|
65
|
-
() => (orchestrationGraph ? getNextCanonicalNodeId(orchestrationGraph) : "input"),
|
|
66
|
-
[orchestrationGraph]
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (graphUnset || graphBlankShell) return;
|
|
71
|
-
setOrchestrationGraph((current) => {
|
|
72
|
-
const base = buildDefaultOrchestrationGraphFromRegistry(registryRow, {
|
|
73
|
-
label: registryName,
|
|
74
|
-
authRef,
|
|
75
|
-
rootPath
|
|
76
|
-
});
|
|
77
|
-
const parsed = parseOrchestrationGraph(current) || current;
|
|
78
|
-
if (!parsed?.nodes?.length) return current;
|
|
79
|
-
return {
|
|
80
|
-
...parsed,
|
|
81
|
-
nodes: parsed.nodes.map((node) => {
|
|
82
|
-
const template = base.nodes.find((n) => n.id === node.id);
|
|
83
|
-
if (!template) return node;
|
|
84
|
-
if (node.id === "api-request") {
|
|
85
|
-
return {
|
|
86
|
-
...node,
|
|
87
|
-
subtitle: template.subtitle,
|
|
88
|
-
config: { ...template.config, ...node.config, authRef }
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
if (node.id === "transform") {
|
|
92
|
-
return { ...node, config: { ...node.config, rootPath } };
|
|
93
|
-
}
|
|
94
|
-
return node;
|
|
95
|
-
})
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
}, [registryKey, registryName, authRef, rootPath, registryRow, integrationId, graphUnset, graphBlankShell]);
|
|
99
|
-
|
|
100
|
-
const selectedNode = useMemo(() => {
|
|
101
|
-
const parsed = parseOrchestrationGraph(orchestrationGraph) || orchestrationGraph;
|
|
102
|
-
if (!selectedNodeId || !parsed?.nodes) return null;
|
|
103
|
-
return parsed.nodes.find((n) => String(n.id) === selectedNodeId) || null;
|
|
104
|
-
}, [orchestrationGraph, selectedNodeId]);
|
|
105
|
-
|
|
106
|
-
const graphSerialized = useMemo(
|
|
107
|
-
() => (graphUnset ? "" : serializeOrchestrationGraph(orchestrationGraph)),
|
|
108
|
-
[orchestrationGraph, graphUnset]
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (graphUnset || graphBlankShell) {
|
|
113
|
-
setGraphError(graphBlankShell ? "Add at least Input and API Registry nodes before creating." : "");
|
|
114
|
-
} else {
|
|
115
|
-
const validation = validateOrchestrationGraph(orchestrationGraph);
|
|
116
|
-
setGraphError(validation.ok ? "" : validation.errors[0] || "Invalid graph");
|
|
117
|
-
}
|
|
118
|
-
onDraftChange?.({
|
|
119
|
-
name,
|
|
120
|
-
description,
|
|
121
|
-
runLocality,
|
|
122
|
-
adapter,
|
|
123
|
-
authRef,
|
|
124
|
-
envRefs,
|
|
125
|
-
networkAllow,
|
|
126
|
-
timeoutMs,
|
|
127
|
-
rootPath,
|
|
128
|
-
instructions,
|
|
129
|
-
agentHost,
|
|
130
|
-
schedulerRegistryId,
|
|
131
|
-
orchestrationGraph: graphSerialized
|
|
132
|
-
});
|
|
133
|
-
}, [
|
|
134
|
-
name,
|
|
135
|
-
description,
|
|
136
|
-
runLocality,
|
|
137
|
-
adapter,
|
|
138
|
-
authRef,
|
|
139
|
-
envRefs,
|
|
140
|
-
networkAllow,
|
|
141
|
-
timeoutMs,
|
|
142
|
-
rootPath,
|
|
143
|
-
instructions,
|
|
144
|
-
agentHost,
|
|
145
|
-
schedulerRegistryId,
|
|
146
|
-
graphSerialized,
|
|
147
|
-
orchestrationGraph,
|
|
148
|
-
graphUnset,
|
|
149
|
-
graphBlankShell,
|
|
150
|
-
onDraftChange
|
|
151
|
-
]);
|
|
152
|
-
|
|
153
|
-
function startFromRegistry() {
|
|
154
|
-
setOrchestrationGraph(buildDefaultOrchestrationGraphFromRegistry(registryRow, { authRef, rootPath }));
|
|
155
|
-
setSelectedNodeId("input");
|
|
156
|
-
setConfigTab("node");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function startBlank() {
|
|
160
|
-
setOrchestrationGraph(buildBlankOrchestrationGraphShell());
|
|
161
|
-
setSelectedNodeId("input");
|
|
162
|
-
setConfigTab("node");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function applyPastedGraph(text) {
|
|
166
|
-
const parsed = parseOrchestrationGraph(text);
|
|
167
|
-
if (parsed) setOrchestrationGraph(parsed);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function addNextNode() {
|
|
171
|
-
if (!nextNodeId) return;
|
|
172
|
-
setOrchestrationGraph((g) => addCanonicalNodeToGraph(
|
|
173
|
-
g || buildBlankOrchestrationGraphShell(),
|
|
174
|
-
nextNodeId,
|
|
175
|
-
registryRow,
|
|
176
|
-
{ authRef, rootPath }
|
|
177
|
-
));
|
|
178
|
-
setSelectedNodeId(nextNodeId);
|
|
179
|
-
setConfigTab("node");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function handleNodeConfigChange(configPatch) {
|
|
183
|
-
if (!selectedNodeId) return;
|
|
184
|
-
setOrchestrationGraph((g) => updateGraphNode(g, selectedNodeId, configPatch));
|
|
185
|
-
if (selectedNodeId === "transform" && configPatch.rootPath) {
|
|
186
|
-
setRootPath(String(configPatch.rootPath));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function handleConnectorAction(payload) {
|
|
191
|
-
const { nodeId, tab } = resolveConnectorAction(payload);
|
|
192
|
-
setSelectedNodeId(nodeId);
|
|
193
|
-
setConfigTab(tab);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const defaultInstructions = `Governed sandbox tool for ${registryName}. Calls ${String(registryRow?.method || "GET").toUpperCase()} ${registryRow?.endpoint || registryRow?.baseUrl || ""}. authRef ${authRef} only — secrets resolve server-side.`;
|
|
197
|
-
const headerBadge = isApiRegistryTestSuccessful(registryRow) ? "connected" : "draft";
|
|
198
|
-
const canCreate = graphUiState === "populated" && !graphError && isApiRegistryTestSuccessful(registryRow);
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<section className="dm-orchestration-sidecar" aria-label="Sandbox orchestration field editor">
|
|
202
|
-
<header className="dm-orchestration-header">
|
|
203
|
-
<button type="button" className="dm-orchestration-header__back" onClick={onCancel} aria-label="Back">
|
|
204
|
-
<ArrowLeft size={16} />
|
|
205
|
-
</button>
|
|
206
|
-
<div className="dm-orchestration-header__titles">
|
|
207
|
-
<h2>Sandbox tool draft</h2>
|
|
208
|
-
<p>Created from {registryName}</p>
|
|
209
|
-
</div>
|
|
210
|
-
<span className={`dm-orchestration-header__badge is-${headerBadge}`}>{headerBadge}</span>
|
|
211
|
-
<div className="dm-orchestration-header__actions">
|
|
212
|
-
<button type="button" className="dm-btn-outline" disabled={disabled} onClick={onCancel}>
|
|
213
|
-
Cancel
|
|
214
|
-
</button>
|
|
215
|
-
<button
|
|
216
|
-
type="button"
|
|
217
|
-
className="dm-btn-primary-sm"
|
|
218
|
-
disabled={disabled || !name.trim() || !canCreate}
|
|
219
|
-
onClick={onRequestConfirm}
|
|
220
|
-
>
|
|
221
|
-
Create tool
|
|
222
|
-
</button>
|
|
223
|
-
</div>
|
|
224
|
-
</header>
|
|
225
|
-
|
|
226
|
-
<div className="dm-orchestration-sidecar__body">
|
|
227
|
-
<div className="dm-orchestration-sidecar__canvas-col">
|
|
228
|
-
{graphUnset ? (
|
|
229
|
-
<OrchestrationGraphEmptyCanvas
|
|
230
|
-
disabled={disabled}
|
|
231
|
-
onStartFromRegistry={startFromRegistry}
|
|
232
|
-
onStartBlank={startBlank}
|
|
233
|
-
onPasteGraph={applyPastedGraph}
|
|
234
|
-
/>
|
|
235
|
-
) : graphBlankShell ? (
|
|
236
|
-
<div className="dm-orchestration-canvas dm-orchestration-canvas--blank-shell">
|
|
237
|
-
<p className="dm-orchestration-canvas__blank-hint">Add first node</p>
|
|
238
|
-
<button type="button" className="dm-btn-outline" disabled={disabled} onClick={addNextNode}>
|
|
239
|
-
+ Add Input
|
|
240
|
-
</button>
|
|
241
|
-
</div>
|
|
242
|
-
) : (
|
|
243
|
-
<>
|
|
244
|
-
<OrchestrationGraphCanvas
|
|
245
|
-
graph={orchestrationGraph}
|
|
246
|
-
selectedNodeId={selectedNodeId}
|
|
247
|
-
onSelectNode={(node) => {
|
|
248
|
-
setSelectedNodeId(String(node?.id || ""));
|
|
249
|
-
setConfigTab("node");
|
|
250
|
-
}}
|
|
251
|
-
onConnectorAction={handleConnectorAction}
|
|
252
|
-
/>
|
|
253
|
-
{nextNodeId && (
|
|
254
|
-
<button
|
|
255
|
-
type="button"
|
|
256
|
-
className="dm-btn-outline dm-orchestration-canvas__add-node"
|
|
257
|
-
disabled={disabled}
|
|
258
|
-
onClick={addNextNode}
|
|
259
|
-
>
|
|
260
|
-
+ Add {nextNodeId === "api-request" ? "API Registry" : nextNodeId === "transform" ? "Transform" : nextNodeId === "result" ? "Result" : "Input"}
|
|
261
|
-
</button>
|
|
262
|
-
)}
|
|
263
|
-
</>
|
|
264
|
-
)}
|
|
265
|
-
</div>
|
|
266
|
-
|
|
267
|
-
<div className="dm-orchestration-sidecar__config-col">
|
|
268
|
-
{graphUiState === "populated" && (
|
|
269
|
-
<OrchestrationNodeConfigPanel
|
|
270
|
-
node={selectedNode}
|
|
271
|
-
registryRow={registryRow}
|
|
272
|
-
disabled={disabled}
|
|
273
|
-
activeTab={configTab}
|
|
274
|
-
onTabChange={setConfigTab}
|
|
275
|
-
onConfigChange={handleNodeConfigChange}
|
|
276
|
-
/>
|
|
277
|
-
)}
|
|
278
|
-
|
|
279
|
-
<details className="dm-orchestration-runtime">
|
|
280
|
-
<summary>Runtime (sandbox row)</summary>
|
|
281
|
-
<div className="dm-orchestration-runtime__fields">
|
|
282
|
-
<label className="dm-orchestration-config__field">
|
|
283
|
-
<span>Name</span>
|
|
284
|
-
<input value={name} disabled={disabled} onChange={(e) => setName(e.target.value)} />
|
|
285
|
-
</label>
|
|
286
|
-
<label className="dm-orchestration-config__field">
|
|
287
|
-
<span>Description</span>
|
|
288
|
-
<textarea rows={2} value={description} disabled={disabled} onChange={(e) => setDescription(e.target.value)} />
|
|
289
|
-
</label>
|
|
290
|
-
<label className="dm-orchestration-config__field">
|
|
291
|
-
<span>Run locality</span>
|
|
292
|
-
<select
|
|
293
|
-
value={runLocality}
|
|
294
|
-
disabled={disabled}
|
|
295
|
-
onChange={(e) => {
|
|
296
|
-
const next = e.target.value;
|
|
297
|
-
setRunLocality(next);
|
|
298
|
-
setAdapter(next === "serverless" ? "serverless" : "local-process");
|
|
299
|
-
if (next === "serverless" && !schedulerRegistryId) {
|
|
300
|
-
setSchedulerRegistryId(integrationId);
|
|
301
|
-
}
|
|
302
|
-
}}
|
|
303
|
-
>
|
|
304
|
-
<option value="local">local</option>
|
|
305
|
-
<option value="serverless">serverless</option>
|
|
306
|
-
</select>
|
|
307
|
-
</label>
|
|
308
|
-
<label className="dm-orchestration-config__field">
|
|
309
|
-
<span>Adapter</span>
|
|
310
|
-
<select value={adapter} disabled={disabled} onChange={(e) => setAdapter(e.target.value)}>
|
|
311
|
-
<option value="local-process">local-process</option>
|
|
312
|
-
<option value="local-agent-host">local-agent-host</option>
|
|
313
|
-
<option value="serverless">serverless</option>
|
|
314
|
-
</select>
|
|
315
|
-
</label>
|
|
316
|
-
{adapter === "local-agent-host" && (
|
|
317
|
-
<label className="dm-orchestration-config__field">
|
|
318
|
-
<span>Agent host</span>
|
|
319
|
-
<input value={agentHost} disabled={disabled} onChange={(e) => setAgentHost(e.target.value)} />
|
|
320
|
-
</label>
|
|
321
|
-
)}
|
|
322
|
-
{runLocality === "serverless" && (
|
|
323
|
-
<label className="dm-orchestration-config__field">
|
|
324
|
-
<span>Scheduler registry ID</span>
|
|
325
|
-
<input
|
|
326
|
-
value={schedulerRegistryId}
|
|
327
|
-
disabled={disabled}
|
|
328
|
-
onChange={(e) => setSchedulerRegistryId(e.target.value)}
|
|
329
|
-
/>
|
|
330
|
-
</label>
|
|
331
|
-
)}
|
|
332
|
-
<label className="dm-orchestration-config__field">
|
|
333
|
-
<span>Auth reference</span>
|
|
334
|
-
<input value={authRef} disabled={disabled} onChange={(e) => setAuthRef(e.target.value)} />
|
|
335
|
-
</label>
|
|
336
|
-
<label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
|
|
337
|
-
<input
|
|
338
|
-
type="checkbox"
|
|
339
|
-
checked={networkAllow}
|
|
340
|
-
disabled={disabled}
|
|
341
|
-
onChange={(e) => setNetworkAllow(e.target.checked)}
|
|
342
|
-
/>
|
|
343
|
-
<span>Network allowed</span>
|
|
344
|
-
</label>
|
|
345
|
-
<label className="dm-orchestration-config__field">
|
|
346
|
-
<span>Env refs (comma-separated)</span>
|
|
347
|
-
<input value={envRefs} disabled={disabled} onChange={(e) => setEnvRefs(e.target.value)} />
|
|
348
|
-
</label>
|
|
349
|
-
<label className="dm-orchestration-config__field">
|
|
350
|
-
<span>Timeout (ms)</span>
|
|
351
|
-
<input value={timeoutMs} disabled={disabled} onChange={(e) => setTimeoutMs(e.target.value)} />
|
|
352
|
-
</label>
|
|
353
|
-
<label className="dm-orchestration-config__field">
|
|
354
|
-
<span>Instructions</span>
|
|
355
|
-
<textarea
|
|
356
|
-
rows={3}
|
|
357
|
-
value={instructions || defaultInstructions}
|
|
358
|
-
disabled={disabled}
|
|
359
|
-
onChange={(e) => setInstructions(e.target.value)}
|
|
360
|
-
/>
|
|
361
|
-
</label>
|
|
362
|
-
</div>
|
|
363
|
-
</details>
|
|
364
|
-
|
|
365
|
-
{graphError && <p className="dm-orchestration-config__error">{graphError}</p>}
|
|
366
|
-
{graphUnset && (
|
|
367
|
-
<p className="dm-orchestration-config__hint">Start a graph before creating the sandbox tool.</p>
|
|
368
|
-
)}
|
|
369
|
-
<p className="dm-orchestration-sidecar__footnote">
|
|
370
|
-
No secrets are stored. Nothing runs until you click Run sandbox after creation.
|
|
371
|
-
</p>
|
|
372
|
-
</div>
|
|
373
|
-
</div>
|
|
374
|
-
</section>
|
|
375
|
-
);
|
|
376
|
-
}
|