@growthub/cli 0.13.2 → 0.13.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/workspace/metadata-graph/route.js +184 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +24 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/route.js +14 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/login/route.js +74 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/logout/route.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-agent-auth/status/route.js +77 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +72 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +123 -27
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +224 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +754 -92
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxAgentAuthPanel.jsx +224 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SandboxRunPanel.jsx +32 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +530 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/page.jsx +8 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +10 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/RunSetupPanel.jsx +261 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +119 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +779 -138
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +91 -14
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/sandbox-environment-primitive.md +35 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +28 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +216 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +412 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +366 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +34 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-eligibility.js +50 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth-redaction.js +64 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +665 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-host-catalog.js +168 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +595 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +164 -7
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +11 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +111 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Runs Console V2 — manual run input contract.
|
|
3
|
+
*
|
|
4
|
+
* Pure helper for discovering, validating, normalizing, and redacting the
|
|
5
|
+
* manual run-input envelope used by form / human-input nodes in the
|
|
6
|
+
* orchestration graph.
|
|
7
|
+
*
|
|
8
|
+
* Contract:
|
|
9
|
+
*
|
|
10
|
+
* {
|
|
11
|
+
* "kind": "growthub-workflow-run-inputs-v1",
|
|
12
|
+
* "source": "manual",
|
|
13
|
+
* "values": { "<fieldId>": "<safe string>" },
|
|
14
|
+
* "files": [{ "id": "<attachmentId>", "name": "...", "size": <bytes> }]
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Invariants:
|
|
18
|
+
* - No React, no fetch, no workspace mutation.
|
|
19
|
+
* - Secret-looking values (Bearer tokens, api_key= patterns, password=, etc.)
|
|
20
|
+
* are redacted before display and before persistence.
|
|
21
|
+
* - Field IDs marked as `secret` are stripped to `secretRef` only — raw
|
|
22
|
+
* values are never accepted from the browser into a run record.
|
|
23
|
+
* - Backward compatible: workflows without human-input nodes return
|
|
24
|
+
* { requiresInput: false } and the rest of the pipeline behaves as it
|
|
25
|
+
* did in V1.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { parseOrchestrationGraph, redactSecretsFromText } from "./orchestration-graph.js";
|
|
29
|
+
|
|
30
|
+
const RUN_INPUTS_KIND = "growthub-workflow-run-inputs-v1";
|
|
31
|
+
const MAX_RUN_INPUT_VALUES = 64;
|
|
32
|
+
const MAX_RUN_INPUT_FIELD_BYTES = 8 * 1024;
|
|
33
|
+
const MAX_RUN_INPUT_TOTAL_BYTES = 64 * 1024;
|
|
34
|
+
const MAX_RUN_INPUT_FILES = 16;
|
|
35
|
+
|
|
36
|
+
const KNOWN_FIELD_TYPES = new Set([
|
|
37
|
+
"text",
|
|
38
|
+
"string",
|
|
39
|
+
"email",
|
|
40
|
+
"url",
|
|
41
|
+
"number",
|
|
42
|
+
"integer",
|
|
43
|
+
"textarea",
|
|
44
|
+
"json",
|
|
45
|
+
"boolean",
|
|
46
|
+
"checkbox",
|
|
47
|
+
"secretRef",
|
|
48
|
+
"secret-ref",
|
|
49
|
+
"select"
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const SECRETISH_KEYS = /^(api[_-]?key|token|password|secret|authorization|bearer)$/i;
|
|
53
|
+
|
|
54
|
+
function safeString(value) {
|
|
55
|
+
if (value == null) return "";
|
|
56
|
+
return typeof value === "string" ? value : String(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function byteLength(value) {
|
|
60
|
+
try {
|
|
61
|
+
return Buffer.byteLength(safeString(value), "utf8");
|
|
62
|
+
} catch {
|
|
63
|
+
return safeString(value).length;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseFieldDescriptor(entry) {
|
|
68
|
+
if (!entry || typeof entry !== "object") return null;
|
|
69
|
+
const idRaw = safeString(entry.key || entry.id || entry.name).trim();
|
|
70
|
+
if (!idRaw) return null;
|
|
71
|
+
const id = idRaw.replace(/\s+/g, "_");
|
|
72
|
+
const descriptor = safeString(entry.value ?? entry.type ?? entry.help).trim();
|
|
73
|
+
const lower = descriptor.toLowerCase();
|
|
74
|
+
let type = "text";
|
|
75
|
+
let helpText = "";
|
|
76
|
+
if (KNOWN_FIELD_TYPES.has(lower)) {
|
|
77
|
+
type = lower === "secret-ref" ? "secretRef" : lower;
|
|
78
|
+
} else if (descriptor) {
|
|
79
|
+
helpText = descriptor;
|
|
80
|
+
}
|
|
81
|
+
const isSecret = type === "secretRef" || SECRETISH_KEYS.test(id);
|
|
82
|
+
return {
|
|
83
|
+
id,
|
|
84
|
+
label: safeString(entry.label || idRaw).trim() || id,
|
|
85
|
+
type: isSecret ? "secretRef" : type,
|
|
86
|
+
required: entry.required !== false && entry.optional !== true,
|
|
87
|
+
helpText,
|
|
88
|
+
isSecret
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function discoverRunInputSchema(graphValue) {
|
|
93
|
+
const graph = parseOrchestrationGraph(graphValue) || graphValue || null;
|
|
94
|
+
const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
95
|
+
const fields = [];
|
|
96
|
+
const seen = new Set();
|
|
97
|
+
let requiresInput = false;
|
|
98
|
+
let title = "";
|
|
99
|
+
let instructions = "";
|
|
100
|
+
|
|
101
|
+
for (const node of nodes) {
|
|
102
|
+
const type = safeString(node?.type).trim();
|
|
103
|
+
const action = safeString(node?.config?.action).trim();
|
|
104
|
+
const isHumanInputForm = type === "human-input" || action === "form";
|
|
105
|
+
if (!isHumanInputForm) continue;
|
|
106
|
+
const config = node?.config && typeof node.config === "object" ? node.config : {};
|
|
107
|
+
if (!title) title = safeString(config.title).trim();
|
|
108
|
+
if (!instructions) instructions = safeString(config.instructions).trim();
|
|
109
|
+
const requiredFlag = config.required !== false && config.requiresInput !== false;
|
|
110
|
+
if (requiredFlag) requiresInput = true;
|
|
111
|
+
const rawFields = Array.isArray(config.fields) ? config.fields : [];
|
|
112
|
+
for (const raw of rawFields) {
|
|
113
|
+
const descriptor = parseFieldDescriptor(raw);
|
|
114
|
+
if (!descriptor) continue;
|
|
115
|
+
if (seen.has(descriptor.id)) continue;
|
|
116
|
+
seen.add(descriptor.id);
|
|
117
|
+
if (!requiredFlag) descriptor.required = false;
|
|
118
|
+
fields.push(descriptor);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
kind: RUN_INPUTS_KIND,
|
|
124
|
+
requiresInput: requiresInput && fields.length > 0,
|
|
125
|
+
title: title || "Run inputs",
|
|
126
|
+
instructions,
|
|
127
|
+
fields
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function coerceFieldValue(field, raw) {
|
|
132
|
+
if (raw == null) return "";
|
|
133
|
+
if (field?.isSecret || field?.type === "secretRef") {
|
|
134
|
+
if (raw && typeof raw === "object" && "secretRef" in raw) return raw;
|
|
135
|
+
return safeString(raw);
|
|
136
|
+
}
|
|
137
|
+
if (field.type === "boolean" || field.type === "checkbox") {
|
|
138
|
+
if (typeof raw === "boolean") return raw;
|
|
139
|
+
const text = safeString(raw).trim().toLowerCase();
|
|
140
|
+
return ["1", "true", "yes", "on"].includes(text);
|
|
141
|
+
}
|
|
142
|
+
if (field.type === "number" || field.type === "integer") {
|
|
143
|
+
const num = Number(raw);
|
|
144
|
+
return Number.isFinite(num) ? num : "";
|
|
145
|
+
}
|
|
146
|
+
if (field.type === "json") {
|
|
147
|
+
if (typeof raw === "string") {
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(raw);
|
|
150
|
+
} catch {
|
|
151
|
+
return raw.slice(0, MAX_RUN_INPUT_FIELD_BYTES);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return raw;
|
|
155
|
+
}
|
|
156
|
+
return safeString(raw);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function redactValueForPersistence(field, value) {
|
|
160
|
+
if (field?.isSecret) {
|
|
161
|
+
if (value && typeof value === "object" && value.secretRef) {
|
|
162
|
+
return { secretRef: safeString(value.secretRef).trim() };
|
|
163
|
+
}
|
|
164
|
+
return { secretRef: "[redacted]" };
|
|
165
|
+
}
|
|
166
|
+
if (typeof value === "string") {
|
|
167
|
+
const truncated = value.length > MAX_RUN_INPUT_FIELD_BYTES
|
|
168
|
+
? `${value.slice(0, MAX_RUN_INPUT_FIELD_BYTES)}…`
|
|
169
|
+
: value;
|
|
170
|
+
return redactSecretsFromText(truncated);
|
|
171
|
+
}
|
|
172
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
173
|
+
if (value == null) return "";
|
|
174
|
+
try {
|
|
175
|
+
return JSON.parse(redactSecretsFromText(JSON.stringify(value)));
|
|
176
|
+
} catch {
|
|
177
|
+
return "";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function normalizeFile(entry) {
|
|
182
|
+
if (!entry || typeof entry !== "object") return null;
|
|
183
|
+
const id = safeString(entry.id || entry.attachmentId).trim();
|
|
184
|
+
if (!id) return null;
|
|
185
|
+
const name = safeString(entry.name || entry.filename || "").trim();
|
|
186
|
+
const size = Number(entry.size);
|
|
187
|
+
return {
|
|
188
|
+
id,
|
|
189
|
+
name,
|
|
190
|
+
size: Number.isFinite(size) && size >= 0 ? size : 0
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function validateRunInputsEnvelope(envelope, schema) {
|
|
195
|
+
if (envelope == null) return { ok: true, missing: [], unknown: [] };
|
|
196
|
+
if (typeof envelope !== "object" || Array.isArray(envelope)) {
|
|
197
|
+
return { ok: false, error: "runInputs must be an object", missing: [], unknown: [] };
|
|
198
|
+
}
|
|
199
|
+
const knownFieldIds = new Set((schema?.fields || []).map((f) => f.id));
|
|
200
|
+
const values = envelope.values && typeof envelope.values === "object" && !Array.isArray(envelope.values)
|
|
201
|
+
? envelope.values
|
|
202
|
+
: {};
|
|
203
|
+
const valueKeys = Object.keys(values);
|
|
204
|
+
if (valueKeys.length > MAX_RUN_INPUT_VALUES) {
|
|
205
|
+
return { ok: false, error: `runInputs.values exceeds ${MAX_RUN_INPUT_VALUES} fields`, missing: [], unknown: [] };
|
|
206
|
+
}
|
|
207
|
+
let totalBytes = 0;
|
|
208
|
+
for (const key of valueKeys) {
|
|
209
|
+
const bytes = byteLength(values[key]);
|
|
210
|
+
if (bytes > MAX_RUN_INPUT_FIELD_BYTES) {
|
|
211
|
+
return { ok: false, error: `runInputs.values["${key}"] exceeds ${MAX_RUN_INPUT_FIELD_BYTES} bytes`, missing: [], unknown: [] };
|
|
212
|
+
}
|
|
213
|
+
totalBytes += bytes;
|
|
214
|
+
}
|
|
215
|
+
if (totalBytes > MAX_RUN_INPUT_TOTAL_BYTES) {
|
|
216
|
+
return { ok: false, error: `runInputs.values exceeds ${MAX_RUN_INPUT_TOTAL_BYTES} bytes`, missing: [], unknown: [] };
|
|
217
|
+
}
|
|
218
|
+
const files = Array.isArray(envelope.files) ? envelope.files : [];
|
|
219
|
+
if (files.length > MAX_RUN_INPUT_FILES) {
|
|
220
|
+
return { ok: false, error: `runInputs.files exceeds ${MAX_RUN_INPUT_FILES} entries`, missing: [], unknown: [] };
|
|
221
|
+
}
|
|
222
|
+
const missing = (schema?.fields || [])
|
|
223
|
+
.filter((f) => f.required && !(f.id in values))
|
|
224
|
+
.map((f) => f.id);
|
|
225
|
+
const unknown = valueKeys.filter((id) => knownFieldIds.size > 0 && !knownFieldIds.has(id));
|
|
226
|
+
return { ok: missing.length === 0, missing, unknown };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function normalizeRunInputsEnvelope(value, schema) {
|
|
230
|
+
if (value == null) return null;
|
|
231
|
+
if (typeof value !== "object" || Array.isArray(value)) return null;
|
|
232
|
+
const schemaFields = Array.isArray(schema?.fields) ? schema.fields : [];
|
|
233
|
+
const fieldsById = new Map(schemaFields.map((f) => [f.id, f]));
|
|
234
|
+
const rawValues = value.values && typeof value.values === "object" && !Array.isArray(value.values)
|
|
235
|
+
? value.values
|
|
236
|
+
: {};
|
|
237
|
+
const safeValues = {};
|
|
238
|
+
for (const [key, raw] of Object.entries(rawValues)) {
|
|
239
|
+
const id = safeString(key).trim();
|
|
240
|
+
if (!id) continue;
|
|
241
|
+
const field = fieldsById.get(id) || parseFieldDescriptor({ key: id, value: "" });
|
|
242
|
+
const coerced = coerceFieldValue(field, raw);
|
|
243
|
+
safeValues[id] = redactValueForPersistence(field, coerced);
|
|
244
|
+
}
|
|
245
|
+
const rawFiles = Array.isArray(value.files) ? value.files : [];
|
|
246
|
+
const files = rawFiles.map(normalizeFile).filter(Boolean).slice(0, MAX_RUN_INPUT_FILES);
|
|
247
|
+
const source = safeString(value.source || "manual").trim() || "manual";
|
|
248
|
+
return {
|
|
249
|
+
kind: RUN_INPUTS_KIND,
|
|
250
|
+
source,
|
|
251
|
+
values: safeValues,
|
|
252
|
+
files
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function redactRunInputsEnvelope(value) {
|
|
257
|
+
if (!value || typeof value !== "object") return null;
|
|
258
|
+
const fields = Array.isArray(value.fields) ? value.fields : [];
|
|
259
|
+
const fieldsById = new Map(fields.map((f) => [f.id || f.key, f]));
|
|
260
|
+
const out = {
|
|
261
|
+
kind: safeString(value.kind || RUN_INPUTS_KIND).trim() || RUN_INPUTS_KIND,
|
|
262
|
+
source: safeString(value.source || "manual").trim() || "manual",
|
|
263
|
+
values: {},
|
|
264
|
+
files: []
|
|
265
|
+
};
|
|
266
|
+
const rawValues = value.values && typeof value.values === "object" && !Array.isArray(value.values)
|
|
267
|
+
? value.values
|
|
268
|
+
: {};
|
|
269
|
+
for (const [key, raw] of Object.entries(rawValues)) {
|
|
270
|
+
const field = fieldsById.get(key) || { id: key, isSecret: SECRETISH_KEYS.test(key) };
|
|
271
|
+
out.values[key] = redactValueForPersistence(field, raw);
|
|
272
|
+
}
|
|
273
|
+
const rawFiles = Array.isArray(value.files) ? value.files : [];
|
|
274
|
+
out.files = rawFiles.map(normalizeFile).filter(Boolean);
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function summarizeRunInputs(value) {
|
|
279
|
+
if (!value || typeof value !== "object") {
|
|
280
|
+
return { source: "", fieldCount: 0, fileCount: 0, fieldIds: [] };
|
|
281
|
+
}
|
|
282
|
+
const values = value.values && typeof value.values === "object" && !Array.isArray(value.values)
|
|
283
|
+
? value.values
|
|
284
|
+
: {};
|
|
285
|
+
const files = Array.isArray(value.files) ? value.files : [];
|
|
286
|
+
const fieldIds = Object.keys(values);
|
|
287
|
+
return {
|
|
288
|
+
source: safeString(value.source || "manual").trim() || "manual",
|
|
289
|
+
fieldCount: fieldIds.length,
|
|
290
|
+
fileCount: files.length,
|
|
291
|
+
fieldIds
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function buildInputPayloadForRunner(envelope) {
|
|
296
|
+
if (!envelope || typeof envelope !== "object") return {};
|
|
297
|
+
const values = envelope.values && typeof envelope.values === "object" && !Array.isArray(envelope.values)
|
|
298
|
+
? envelope.values
|
|
299
|
+
: {};
|
|
300
|
+
const out = {};
|
|
301
|
+
for (const [key, raw] of Object.entries(values)) {
|
|
302
|
+
if (raw && typeof raw === "object" && "secretRef" in raw) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
out[key] = raw;
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Workspace Metadata Graph V1 — typed run-input descriptors.
|
|
312
|
+
*
|
|
313
|
+
* Returns a metadata-compatible list:
|
|
314
|
+
* { id, label, type, required, secretRefOnly, sourceNodeId, workflowId }
|
|
315
|
+
*
|
|
316
|
+
* Used by the metadata store + workflow sidecar so the manual run input
|
|
317
|
+
* schema is a single typed contract — never re-derived inside UI code.
|
|
318
|
+
*
|
|
319
|
+
* Existing redaction + size limits remain enforced by
|
|
320
|
+
* `validateRunInputsEnvelope` / `normalizeRunInputsEnvelope`. This helper
|
|
321
|
+
* is descriptor-only and does not mutate or echo any field value.
|
|
322
|
+
*/
|
|
323
|
+
function describeRunInputMetadataItems({ workflowId, graph, objectId, rowId } = {}) {
|
|
324
|
+
const schema = discoverRunInputSchema(graph);
|
|
325
|
+
const fields = Array.isArray(schema?.fields) ? schema.fields : [];
|
|
326
|
+
const workflowKey = String(workflowId || (objectId && rowId ? `${objectId}::${rowId}` : "")).trim();
|
|
327
|
+
const sourceNodeId = findFirstHumanInputNodeId(graph);
|
|
328
|
+
return fields.map((field) => ({
|
|
329
|
+
kind: "workspaceRunInput",
|
|
330
|
+
id: field.id,
|
|
331
|
+
label: field.label,
|
|
332
|
+
type: field.type,
|
|
333
|
+
required: Boolean(field.required),
|
|
334
|
+
isSecret: Boolean(field.isSecret),
|
|
335
|
+
secretRefOnly: Boolean(field.isSecret) || field.type === "secretRef",
|
|
336
|
+
sourceNodeId,
|
|
337
|
+
workflowId: workflowKey
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function findFirstHumanInputNodeId(graphValue) {
|
|
342
|
+
const graph = parseOrchestrationGraph(graphValue) || graphValue || null;
|
|
343
|
+
const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
|
344
|
+
for (const node of nodes) {
|
|
345
|
+
const type = String(node?.type || "").trim();
|
|
346
|
+
const action = String(node?.config?.action || "").trim();
|
|
347
|
+
if (type === "human-input" || action === "form") return String(node?.id || "").trim() || "human-input";
|
|
348
|
+
}
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export {
|
|
353
|
+
RUN_INPUTS_KIND,
|
|
354
|
+
MAX_RUN_INPUT_VALUES,
|
|
355
|
+
MAX_RUN_INPUT_FIELD_BYTES,
|
|
356
|
+
MAX_RUN_INPUT_TOTAL_BYTES,
|
|
357
|
+
MAX_RUN_INPUT_FILES,
|
|
358
|
+
discoverRunInputSchema,
|
|
359
|
+
normalizeRunInputsEnvelope,
|
|
360
|
+
redactRunInputsEnvelope,
|
|
361
|
+
summarizeRunInputs,
|
|
362
|
+
validateRunInputsEnvelope,
|
|
363
|
+
buildInputPayloadForRunner,
|
|
364
|
+
describeRunInputMetadataItems,
|
|
365
|
+
parseFieldDescriptor
|
|
366
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { redactSecretsFromText } from "./orchestration-graph.js";
|
|
6
|
+
import { redactRunInputsEnvelope, summarizeRunInputs } from "./orchestration-run-inputs.js";
|
|
6
7
|
|
|
7
8
|
function safeParseJson(text) {
|
|
8
9
|
const raw = String(text || "").trim();
|
|
@@ -31,10 +32,15 @@ function parseSandboxRunTrace(lastResponse) {
|
|
|
31
32
|
output: "",
|
|
32
33
|
ranAt: "",
|
|
33
34
|
envRefsResolved: [],
|
|
34
|
-
envRefsMissing: []
|
|
35
|
+
envRefsMissing: [],
|
|
36
|
+
input: null,
|
|
37
|
+
inputSummary: null,
|
|
38
|
+
exports: null
|
|
35
39
|
};
|
|
36
40
|
}
|
|
37
41
|
const outputRaw = parsed.output ?? parsed.normalizedOutput ?? parsed.response;
|
|
42
|
+
const inputRaw = parsed.input || parsed.runInputs || null;
|
|
43
|
+
const safeInput = inputRaw ? redactRunInputsEnvelope(inputRaw) : null;
|
|
38
44
|
return {
|
|
39
45
|
status: parsed.status || (parsed.exitCode === 0 && !parsed.error ? "connected" : parsed.error ? "failed" : ""),
|
|
40
46
|
runId: String(parsed.runId || "").trim(),
|
|
@@ -51,12 +57,25 @@ function parseSandboxRunTrace(lastResponse) {
|
|
|
51
57
|
),
|
|
52
58
|
ranAt: String(parsed.ranAt || "").trim(),
|
|
53
59
|
envRefsResolved: Array.isArray(parsed.envRefsResolved) ? parsed.envRefsResolved : [],
|
|
54
|
-
envRefsMissing: Array.isArray(parsed.envRefsMissing) ? parsed.envRefsMissing : []
|
|
60
|
+
envRefsMissing: Array.isArray(parsed.envRefsMissing) ? parsed.envRefsMissing : [],
|
|
61
|
+
input: safeInput,
|
|
62
|
+
inputSummary: safeInput ? summarizeRunInputs(safeInput) : null,
|
|
63
|
+
exports: parsed.exports && typeof parsed.exports === "object" ? parsed.exports : null,
|
|
64
|
+
swarm: parsed.swarm && typeof parsed.swarm === "object" ? parsed.swarm : null,
|
|
65
|
+
logTree: Array.isArray(parsed.logTree) ? parsed.logTree : null
|
|
55
66
|
};
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
function normalizeRunRecord(record) {
|
|
59
70
|
if (!record || typeof record !== "object") return null;
|
|
71
|
+
const outputRaw = record.output ?? record.normalizedOutput ?? record.response;
|
|
72
|
+
const outputText = typeof outputRaw === "string"
|
|
73
|
+
? outputRaw
|
|
74
|
+
: outputRaw != null
|
|
75
|
+
? JSON.stringify(outputRaw, null, 2)
|
|
76
|
+
: "";
|
|
77
|
+
const inputRaw = record.input || record.runInputs || null;
|
|
78
|
+
const safeInput = inputRaw ? redactRunInputsEnvelope(inputRaw) : null;
|
|
60
79
|
return {
|
|
61
80
|
runId: String(record.runId || "").trim(),
|
|
62
81
|
ranAt: String(record.ranAt || "").trim(),
|
|
@@ -65,8 +84,20 @@ function normalizeRunRecord(record) {
|
|
|
65
84
|
error: redactSecretsFromText(record.error || ""),
|
|
66
85
|
stdout: redactSecretsFromText(typeof record.stdout === "string" ? record.stdout : ""),
|
|
67
86
|
stderr: redactSecretsFromText(record.stderr || ""),
|
|
87
|
+
output: redactSecretsFromText(outputText),
|
|
88
|
+
runtime: String(record.runtime || "").trim(),
|
|
89
|
+
adapter: String(record.adapter || "").trim(),
|
|
90
|
+
runLocality: String(record.runLocality || "").trim(),
|
|
91
|
+
status: String(record.status || "").trim(),
|
|
92
|
+
envRefsResolved: Array.isArray(record.envRefsResolved) ? record.envRefsResolved : [],
|
|
93
|
+
envRefsMissing: Array.isArray(record.envRefsMissing) ? record.envRefsMissing : [],
|
|
94
|
+
adapterMeta: record.adapterMeta && typeof record.adapterMeta === "object" ? record.adapterMeta : null,
|
|
95
|
+
templateTrace: record.templateTrace && typeof record.templateTrace === "object" ? record.templateTrace : null,
|
|
68
96
|
lifecycleStatus: String(record.lifecycleStatus || "").trim(),
|
|
69
|
-
version: String(record.version || "").trim()
|
|
97
|
+
version: String(record.version || "").trim(),
|
|
98
|
+
input: safeInput,
|
|
99
|
+
inputSummary: safeInput ? summarizeRunInputs(safeInput) : null,
|
|
100
|
+
exports: record.exports && typeof record.exports === "object" ? record.exports : null
|
|
70
101
|
};
|
|
71
102
|
}
|
|
72
103
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure eligibility predicates for the Sandbox Local Agent Auth Onboarding
|
|
3
|
+
* V1 panel. Extracted so it can be imported by both the React client
|
|
4
|
+
* component (`.jsx`) and the Node-side unit tests (`.js` — no JSX runtime,
|
|
5
|
+
* no DOM).
|
|
6
|
+
*
|
|
7
|
+
* The auth panel is host-agnostic: any sandbox row with
|
|
8
|
+
* `adapter: "local-agent-host"`, `runLocality !== "serverless"`, and an
|
|
9
|
+
* `agentHost` that is registered in the host auth catalog renders the
|
|
10
|
+
* panel. Per-host capabilities (login / logout buttons) flow from the
|
|
11
|
+
* catalog — this file only decides whether to render at all.
|
|
12
|
+
*
|
|
13
|
+
* No node-specific imports, no React. Safe to ship in any runtime.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { KNOWN_HOST_AUTH_SLUGS } from "./sandbox-agent-host-catalog.js";
|
|
17
|
+
|
|
18
|
+
const LOCAL_AGENT_HOST_ADAPTER = "local-agent-host";
|
|
19
|
+
// Legacy export kept for code that hard-codes the Claude slug elsewhere.
|
|
20
|
+
const CLAUDE_LOCAL_HOST = "claude_local";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @returns {boolean} true when the row should surface the agent auth panel.
|
|
24
|
+
*/
|
|
25
|
+
function isSandboxLocalAgentHost(row) {
|
|
26
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return false;
|
|
27
|
+
const adapter = String(row.adapter || "").trim();
|
|
28
|
+
const agentHost = String(row.agentHost || "").trim();
|
|
29
|
+
const runLocality = String(row.runLocality || "").trim().toLowerCase();
|
|
30
|
+
if (adapter !== LOCAL_AGENT_HOST_ADAPTER) return false;
|
|
31
|
+
if (!KNOWN_HOST_AUTH_SLUGS.includes(agentHost)) return false;
|
|
32
|
+
if (runLocality === "serverless") return false;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Backwards-compatible alias — Claude-specific predicate. Kept for any
|
|
38
|
+
* import that still references the original name; new code should use
|
|
39
|
+
* `isSandboxLocalAgentHost` so it works for every local-agent-host slug.
|
|
40
|
+
*/
|
|
41
|
+
function isSandboxClaudeLocal(row) {
|
|
42
|
+
return isSandboxLocalAgentHost(row) && String(row?.agentHost || "").trim() === CLAUDE_LOCAL_HOST;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
CLAUDE_LOCAL_HOST,
|
|
47
|
+
LOCAL_AGENT_HOST_ADAPTER,
|
|
48
|
+
isSandboxClaudeLocal,
|
|
49
|
+
isSandboxLocalAgentHost
|
|
50
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure utilities for the sandbox agent auth helper.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so the constants and the redaction function can be imported
|
|
5
|
+
* without pulling in Next.js path-aliased modules (`@/lib/workspace-config`
|
|
6
|
+
* etc.). Tests load this file directly to verify the runtime behaviour of
|
|
7
|
+
* the redaction and status taxonomy.
|
|
8
|
+
*
|
|
9
|
+
* No Next-aliased imports. No node-only APIs. Safe to ship in any runtime.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const KNOWN_AGENT_AUTH_STATUSES = Object.freeze([
|
|
13
|
+
"active", // confirmed authenticated (login exit 0, or `auth status` exit 0)
|
|
14
|
+
"reachable", // CLI installed and callable, but auth NOT yet confirmed
|
|
15
|
+
"stale", // CLI reachable but auth-shaped failure detected
|
|
16
|
+
"missing", // binary not found on PATH
|
|
17
|
+
"checking", // transient UI state during a probe
|
|
18
|
+
"unknown" // indeterminate
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const SAFE_ROW_PATCH_FIELDS = Object.freeze([
|
|
22
|
+
"agentAuthStatus",
|
|
23
|
+
"agentAuthProvider",
|
|
24
|
+
"agentAuthLastChecked",
|
|
25
|
+
"agentAuthLastExitCode",
|
|
26
|
+
"agentAuthLastMessage",
|
|
27
|
+
"agentAuthLastLoginUrl"
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const TOKEN_PATTERNS = [
|
|
31
|
+
/sk-ant-[A-Za-z0-9_-]{8,}/g,
|
|
32
|
+
/sk-[A-Za-z0-9_-]{20,}/g,
|
|
33
|
+
/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
34
|
+
/Bearer\s+[A-Za-z0-9._-]{16,}/gi
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const TOKEN_PREFIX_PATTERNS = [
|
|
38
|
+
/(access[_-]?token["']?\s*[:=]\s*)["']?[^\s"',}]+/gi,
|
|
39
|
+
/(refresh[_-]?token["']?\s*[:=]\s*)["']?[^\s"',}]+/gi,
|
|
40
|
+
/(oauth[_-]?token["']?\s*[:=]\s*)["']?[^\s"',}]+/gi,
|
|
41
|
+
/(session[_-]?(?:key|token)["']?\s*[:=]\s*)["']?[^\s"',}]+/gi,
|
|
42
|
+
/(api[_-]?key["']?\s*[:=]\s*)["']?[^\s"',}]+/gi,
|
|
43
|
+
/(password["']?\s*[:=]\s*)["']?[^\s"',}]+/gi
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function redactSecrets(text) {
|
|
47
|
+
if (typeof text !== "string" || !text) return "";
|
|
48
|
+
let next = text;
|
|
49
|
+
for (const pattern of TOKEN_PATTERNS) {
|
|
50
|
+
next = next.replace(pattern, "[redacted]");
|
|
51
|
+
}
|
|
52
|
+
for (const pattern of TOKEN_PREFIX_PATTERNS) {
|
|
53
|
+
next = next.replace(pattern, "$1[redacted]");
|
|
54
|
+
}
|
|
55
|
+
return next;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
KNOWN_AGENT_AUTH_STATUSES,
|
|
60
|
+
SAFE_ROW_PATCH_FIELDS,
|
|
61
|
+
TOKEN_PATTERNS,
|
|
62
|
+
TOKEN_PREFIX_PATTERNS,
|
|
63
|
+
redactSecrets
|
|
64
|
+
};
|