@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,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Runs Console model layer.
|
|
3
|
+
*
|
|
4
|
+
* Pure, framework-free transformation of sandbox run records (saved under
|
|
5
|
+
* `growthub.source-records.json` and `row.lastResponse`) into the shape the
|
|
6
|
+
* Live Runs Console UI consumes: lifecycle, log tree, timeline, search, and
|
|
7
|
+
* a redacted JSON bundle that can be downloaded client-side.
|
|
8
|
+
*
|
|
9
|
+
* This module deliberately does NOT import React, does NOT call fetch, and
|
|
10
|
+
* does NOT mutate workspace config. It is the seam between the AWaC run
|
|
11
|
+
* substrate (sandbox-run route + source records) and the observability UI.
|
|
12
|
+
*
|
|
13
|
+
* See:
|
|
14
|
+
* - app/api/workspace/sandbox-run/route.js (writes records this consumes)
|
|
15
|
+
* - app/data-model/components/OrchestrationRunTracePanel.jsx (consumer)
|
|
16
|
+
* - lib/orchestration-run-trace.js (lower-level record parser)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { redactSecretsFromText } from "./orchestration-graph.js";
|
|
20
|
+
import { redactRunInputsEnvelope, summarizeRunInputs } from "./orchestration-run-inputs.js";
|
|
21
|
+
|
|
22
|
+
const RUN_LOG_BUNDLE_KIND = "growthub-sandbox-run-log-v1";
|
|
23
|
+
const DEFAULT_EXPORT_TARGETS = Object.freeze([
|
|
24
|
+
"download-json",
|
|
25
|
+
"copy-output",
|
|
26
|
+
"download-stdout",
|
|
27
|
+
"download-stderr",
|
|
28
|
+
"download-normalized-output",
|
|
29
|
+
"download-log-node"
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
function safeString(value) {
|
|
33
|
+
if (value == null) return "";
|
|
34
|
+
return typeof value === "string" ? value : String(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function safeJsonString(value) {
|
|
38
|
+
if (value == null) return "";
|
|
39
|
+
if (typeof value === "string") return value;
|
|
40
|
+
try {
|
|
41
|
+
return JSON.stringify(value, null, 2);
|
|
42
|
+
} catch {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseDateMs(value) {
|
|
48
|
+
const text = safeString(value).trim();
|
|
49
|
+
if (!text) return null;
|
|
50
|
+
const ms = Date.parse(text);
|
|
51
|
+
return Number.isFinite(ms) ? ms : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function clampNumber(value) {
|
|
55
|
+
const n = Number(value);
|
|
56
|
+
return Number.isFinite(n) ? n : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function deriveRunSummary(record) {
|
|
60
|
+
if (!record || typeof record !== "object") {
|
|
61
|
+
return { status: "unknown", ok: false, label: "unknown" };
|
|
62
|
+
}
|
|
63
|
+
const exitCode = clampNumber(record.exitCode);
|
|
64
|
+
const errorText = safeString(record.error).trim();
|
|
65
|
+
const httpStatus = clampNumber(record?.adapterMeta?.httpStatus);
|
|
66
|
+
const aborted = record?.adapterMeta?.aborted === true;
|
|
67
|
+
let status = "unknown";
|
|
68
|
+
let ok = false;
|
|
69
|
+
if (aborted) {
|
|
70
|
+
status = "canceled";
|
|
71
|
+
} else if (exitCode === 0 && !errorText && (httpStatus == null || (httpStatus >= 200 && httpStatus < 300))) {
|
|
72
|
+
status = "completed";
|
|
73
|
+
ok = true;
|
|
74
|
+
} else if (errorText || (exitCode != null && exitCode !== 0)) {
|
|
75
|
+
status = "failed";
|
|
76
|
+
} else if (record?.lifecycleStatus === "executing" || record?.lifecycleStatus === "queued") {
|
|
77
|
+
status = String(record.lifecycleStatus);
|
|
78
|
+
}
|
|
79
|
+
return { status, ok, label: status };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function deriveRunLifecycle(record) {
|
|
83
|
+
if (!record || typeof record !== "object") return [];
|
|
84
|
+
const ranAtMs = parseDateMs(record.ranAt);
|
|
85
|
+
const durationMs = clampNumber(record.durationMs) || 0;
|
|
86
|
+
const finishedAtMs = ranAtMs != null ? ranAtMs + durationMs : null;
|
|
87
|
+
const lifecycle = [];
|
|
88
|
+
if (ranAtMs != null) {
|
|
89
|
+
lifecycle.push({ label: "Triggered", at: new Date(ranAtMs).toISOString(), durationMs: 0 });
|
|
90
|
+
lifecycle.push({ label: "Dequeued", at: new Date(ranAtMs).toISOString(), durationMs: 0 });
|
|
91
|
+
lifecycle.push({ label: "Started", at: new Date(ranAtMs).toISOString(), durationMs });
|
|
92
|
+
if (finishedAtMs != null) {
|
|
93
|
+
lifecycle.push({ label: "Finished", at: new Date(finishedAtMs).toISOString(), durationMs: 0 });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return lifecycle;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildLogChildren(record, summary) {
|
|
100
|
+
const children = [];
|
|
101
|
+
const stdout = safeString(record?.stdout).trim();
|
|
102
|
+
const stderr = safeString(record?.stderr).trim();
|
|
103
|
+
const errorText = safeString(record?.error).trim();
|
|
104
|
+
const output = safeJsonString(record?.output ?? record?.normalizedOutput ?? record?.response).trim();
|
|
105
|
+
const adapterMeta = record?.adapterMeta;
|
|
106
|
+
const durationMs = clampNumber(record?.durationMs) || 0;
|
|
107
|
+
|
|
108
|
+
if (errorText) {
|
|
109
|
+
children.push({
|
|
110
|
+
id: "error",
|
|
111
|
+
label: "error",
|
|
112
|
+
type: "error",
|
|
113
|
+
status: "failed",
|
|
114
|
+
durationMs: 0,
|
|
115
|
+
text: redactSecretsFromText(errorText)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (stdout) {
|
|
119
|
+
children.push({
|
|
120
|
+
id: "stdout",
|
|
121
|
+
label: "stdout",
|
|
122
|
+
type: "stream",
|
|
123
|
+
status: "info",
|
|
124
|
+
durationMs,
|
|
125
|
+
text: redactSecretsFromText(stdout)
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (stderr) {
|
|
129
|
+
children.push({
|
|
130
|
+
id: "stderr",
|
|
131
|
+
label: "stderr",
|
|
132
|
+
type: "stream",
|
|
133
|
+
status: "failed",
|
|
134
|
+
durationMs: 0,
|
|
135
|
+
text: redactSecretsFromText(stderr)
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (output && output !== stdout) {
|
|
139
|
+
children.push({
|
|
140
|
+
id: "normalized-output",
|
|
141
|
+
label: "normalized output",
|
|
142
|
+
type: "output",
|
|
143
|
+
status: "info",
|
|
144
|
+
durationMs: 0,
|
|
145
|
+
text: redactSecretsFromText(output)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (adapterMeta && typeof adapterMeta === "object") {
|
|
149
|
+
children.push({
|
|
150
|
+
id: "adapter-meta",
|
|
151
|
+
label: "adapter meta",
|
|
152
|
+
type: "meta",
|
|
153
|
+
status: "info",
|
|
154
|
+
durationMs: 0,
|
|
155
|
+
text: redactSecretsFromText(safeJsonString(adapterMeta))
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return children;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildRunLogTree(record) {
|
|
162
|
+
if (!record || typeof record !== "object") return [];
|
|
163
|
+
if (Array.isArray(record.logTree) && record.logTree.length > 0) {
|
|
164
|
+
return record.logTree;
|
|
165
|
+
}
|
|
166
|
+
const summary = deriveRunSummary(record);
|
|
167
|
+
const durationMs = clampNumber(record?.durationMs) || 0;
|
|
168
|
+
const attemptChildren = buildLogChildren(record, summary);
|
|
169
|
+
const attemptNode = {
|
|
170
|
+
id: "attempt-1",
|
|
171
|
+
label: "Attempt 1",
|
|
172
|
+
type: "attempt",
|
|
173
|
+
status: summary.status,
|
|
174
|
+
durationMs,
|
|
175
|
+
children: attemptChildren
|
|
176
|
+
};
|
|
177
|
+
const rootNode = {
|
|
178
|
+
id: "root",
|
|
179
|
+
label: safeString(record?.adapter || "agent-run").trim() || "agent-run",
|
|
180
|
+
type: "root",
|
|
181
|
+
status: summary.status,
|
|
182
|
+
durationMs,
|
|
183
|
+
children: [attemptNode]
|
|
184
|
+
};
|
|
185
|
+
return [rootNode];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildExportsForRecord(record, stdoutText, stderrText, outputText) {
|
|
189
|
+
const declared = record?.exports?.available;
|
|
190
|
+
if (Array.isArray(declared) && declared.length > 0) {
|
|
191
|
+
return {
|
|
192
|
+
available: declared.map((id) => safeString(id).trim()).filter(Boolean),
|
|
193
|
+
external: Array.isArray(record?.exports?.external) ? record.exports.external.slice() : []
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const available = ["download-json"];
|
|
197
|
+
if (stdoutText || outputText) available.push("copy-output");
|
|
198
|
+
if (stdoutText) available.push("download-stdout");
|
|
199
|
+
if (stderrText) available.push("download-stderr");
|
|
200
|
+
if (outputText && outputText !== stdoutText) available.push("download-normalized-output");
|
|
201
|
+
available.push("download-log-node");
|
|
202
|
+
return { available, external: [] };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizeRunConsoleRecord(record) {
|
|
206
|
+
if (!record || typeof record !== "object") return null;
|
|
207
|
+
const summary = deriveRunSummary(record);
|
|
208
|
+
const lifecycle = deriveRunLifecycle(record);
|
|
209
|
+
const ranAtMs = parseDateMs(record.ranAt);
|
|
210
|
+
const durationMs = clampNumber(record.durationMs);
|
|
211
|
+
const finishedAt = ranAtMs != null && durationMs != null
|
|
212
|
+
? new Date(ranAtMs + durationMs).toISOString()
|
|
213
|
+
: "";
|
|
214
|
+
const adapterMeta = record?.adapterMeta && typeof record.adapterMeta === "object"
|
|
215
|
+
? record.adapterMeta
|
|
216
|
+
: null;
|
|
217
|
+
const templateTrace = record?.templateTrace && typeof record.templateTrace === "object"
|
|
218
|
+
? record.templateTrace
|
|
219
|
+
: null;
|
|
220
|
+
|
|
221
|
+
const stdoutText = safeString(typeof record.stdout === "string" ? record.stdout : safeJsonString(record.stdout));
|
|
222
|
+
const stderrText = safeString(record.stderr);
|
|
223
|
+
const errorText = safeString(record.error);
|
|
224
|
+
const outputRaw = record.output ?? record.normalizedOutput ?? record.response;
|
|
225
|
+
const outputText = typeof outputRaw === "string" ? outputRaw : safeJsonString(outputRaw);
|
|
226
|
+
const rawInput = record.input || record.runInputs || null;
|
|
227
|
+
const safeInput = rawInput ? redactRunInputsEnvelope(rawInput) : null;
|
|
228
|
+
const inputSummary = safeInput ? summarizeRunInputs(safeInput) : null;
|
|
229
|
+
const exports = buildExportsForRecord(record, stdoutText, stderrText, outputText);
|
|
230
|
+
// Workspace Metadata Graph V1 — safe lineage projection. Names only,
|
|
231
|
+
// no secrets. Lets the Live Runs Console UI render "this run came from
|
|
232
|
+
// sandbox X / workflow Y / adapter Z / agent host A" without re-deriving
|
|
233
|
+
// the relationships from raw fields.
|
|
234
|
+
const lineage = {
|
|
235
|
+
runId: safeString(record.runId).trim(),
|
|
236
|
+
objectId: safeString(record.objectId).trim(),
|
|
237
|
+
sandboxName: safeString(record.name || record.sandboxName).trim(),
|
|
238
|
+
workflowRowId: safeString(record.name || record.sandboxName).trim(),
|
|
239
|
+
workflowMetadataId: safeString(record.objectId).trim() && safeString(record.name || record.sandboxName).trim()
|
|
240
|
+
? `workflow:${safeString(record.objectId).trim()}:${safeString(record.name || record.sandboxName).trim()}`
|
|
241
|
+
: "",
|
|
242
|
+
sandboxMetadataId: safeString(record.objectId).trim() && safeString(record.name || record.sandboxName).trim()
|
|
243
|
+
? `sandbox:${safeString(record.objectId).trim()}:${safeString(record.name || record.sandboxName).trim()}`
|
|
244
|
+
: "",
|
|
245
|
+
adapter: safeString(record.adapter).trim(),
|
|
246
|
+
agentHost: safeString(record.agentHost).trim(),
|
|
247
|
+
runtime: safeString(record.runtime).trim(),
|
|
248
|
+
runLocality: safeString(record.runLocality).trim(),
|
|
249
|
+
inputFieldCount: inputSummary ? inputSummary.fieldCount : 0,
|
|
250
|
+
inputSource: inputSummary ? inputSummary.source : "",
|
|
251
|
+
hasOutput: Boolean(outputText)
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
runId: safeString(record.runId).trim(),
|
|
256
|
+
status: summary.status,
|
|
257
|
+
ok: summary.ok,
|
|
258
|
+
exitCode: clampNumber(record.exitCode),
|
|
259
|
+
ranAt: safeString(record.ranAt).trim(),
|
|
260
|
+
finishedAt,
|
|
261
|
+
durationMs: durationMs == null ? null : durationMs,
|
|
262
|
+
queueMs: 0,
|
|
263
|
+
runtime: safeString(record.runtime).trim(),
|
|
264
|
+
adapter: safeString(record.adapter).trim(),
|
|
265
|
+
runLocality: safeString(record.runLocality).trim(),
|
|
266
|
+
lifecycleStatus: safeString(record.lifecycleStatus).trim(),
|
|
267
|
+
version: safeString(record.version).trim(),
|
|
268
|
+
sourceId: safeString(record.sourceId).trim(),
|
|
269
|
+
lifecycle,
|
|
270
|
+
payload: {
|
|
271
|
+
objectId: safeString(record.objectId).trim(),
|
|
272
|
+
name: safeString(record.name || record.sandboxName).trim(),
|
|
273
|
+
runtime: safeString(record.runtime).trim(),
|
|
274
|
+
adapter: safeString(record.adapter).trim(),
|
|
275
|
+
command: redactSecretsFromText(safeString(record.command)),
|
|
276
|
+
instructions: redactSecretsFromText(safeString(record.instructions)),
|
|
277
|
+
useDraft: Boolean(record.useDraft),
|
|
278
|
+
version: safeString(record.version).trim(),
|
|
279
|
+
schedulerRegistryId: safeString(record.schedulerRegistryId).trim(),
|
|
280
|
+
agentHost: safeString(record.agentHost).trim(),
|
|
281
|
+
timeoutMs: clampNumber(record.timeoutMs),
|
|
282
|
+
runInputs: safeInput,
|
|
283
|
+
inputSource: inputSummary ? inputSummary.source : "",
|
|
284
|
+
inputFieldCount: inputSummary ? inputSummary.fieldCount : 0,
|
|
285
|
+
inputFileCount: inputSummary ? inputSummary.fileCount : 0,
|
|
286
|
+
inputSummary
|
|
287
|
+
},
|
|
288
|
+
exports,
|
|
289
|
+
output: {
|
|
290
|
+
stdout: redactSecretsFromText(stdoutText),
|
|
291
|
+
stderr: redactSecretsFromText(stderrText),
|
|
292
|
+
error: redactSecretsFromText(errorText),
|
|
293
|
+
normalizedOutput: redactSecretsFromText(outputText),
|
|
294
|
+
exitCode: clampNumber(record.exitCode)
|
|
295
|
+
},
|
|
296
|
+
context: {
|
|
297
|
+
envRefsResolved: Array.isArray(record.envRefsResolved) ? record.envRefsResolved.slice() : [],
|
|
298
|
+
envRefsMissing: Array.isArray(record.envRefsMissing) ? record.envRefsMissing.slice() : [],
|
|
299
|
+
networkAllow: Boolean(record.networkAllow),
|
|
300
|
+
allowList: Array.isArray(record.allowList) ? record.allowList.slice() : [],
|
|
301
|
+
adapterMeta,
|
|
302
|
+
templateTrace
|
|
303
|
+
},
|
|
304
|
+
lineage,
|
|
305
|
+
swarm: record.swarm && typeof record.swarm === "object" ? record.swarm : null,
|
|
306
|
+
logTree: buildRunLogTree(record)
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function buildRunTimeline(records) {
|
|
311
|
+
const list = Array.isArray(records) ? records : [];
|
|
312
|
+
const items = list
|
|
313
|
+
.map((record) => {
|
|
314
|
+
const normalized = normalizeRunConsoleRecord(record);
|
|
315
|
+
if (!normalized) return null;
|
|
316
|
+
const startedMs = parseDateMs(normalized.ranAt);
|
|
317
|
+
return {
|
|
318
|
+
runId: normalized.runId,
|
|
319
|
+
status: normalized.status,
|
|
320
|
+
durationMs: normalized.durationMs == null ? 0 : normalized.durationMs,
|
|
321
|
+
startedMs,
|
|
322
|
+
ranAt: normalized.ranAt
|
|
323
|
+
};
|
|
324
|
+
})
|
|
325
|
+
.filter(Boolean);
|
|
326
|
+
|
|
327
|
+
const max = items.reduce((m, it) => Math.max(m, it.durationMs || 0), 0);
|
|
328
|
+
return items.map((it) => ({
|
|
329
|
+
...it,
|
|
330
|
+
barRatio: max > 0 ? Math.min(1, (it.durationMs || 0) / max) : 0
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function nodeMatchesQuery(node, query) {
|
|
335
|
+
const text = String(query || "").trim().toLowerCase();
|
|
336
|
+
if (!text) return true;
|
|
337
|
+
const haystack = [
|
|
338
|
+
node?.id,
|
|
339
|
+
node?.label,
|
|
340
|
+
node?.type,
|
|
341
|
+
node?.status,
|
|
342
|
+
node?.text
|
|
343
|
+
]
|
|
344
|
+
.filter(Boolean)
|
|
345
|
+
.join(" ")
|
|
346
|
+
.toLowerCase();
|
|
347
|
+
return haystack.includes(text);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function nodeIsError(node) {
|
|
351
|
+
if (!node) return false;
|
|
352
|
+
if (node.type === "error") return true;
|
|
353
|
+
if (node.type === "stream" && node.id === "stderr") return true;
|
|
354
|
+
if (node.status === "failed" && node.type !== "stream") return true;
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function filterNodeTree(nodes, predicate) {
|
|
359
|
+
if (!Array.isArray(nodes)) return [];
|
|
360
|
+
const out = [];
|
|
361
|
+
for (const node of nodes) {
|
|
362
|
+
const children = filterNodeTree(node?.children, predicate);
|
|
363
|
+
const selfMatches = predicate(node);
|
|
364
|
+
if (selfMatches || children.length > 0) {
|
|
365
|
+
out.push({ ...node, children });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function filterRunLogTree(tree, { query = "", errorsOnly = false } = {}) {
|
|
372
|
+
return filterNodeTree(tree, (node) => {
|
|
373
|
+
if (errorsOnly && !nodeIsError(node) && !(Array.isArray(node?.children) && node.children.some(nodeIsError))) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
return nodeMatchesQuery(node, query);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function formatRunDuration(ms) {
|
|
381
|
+
const n = clampNumber(ms);
|
|
382
|
+
if (n == null) return "—";
|
|
383
|
+
if (n < 1000) return `${Math.round(n)} ms`;
|
|
384
|
+
if (n < 60_000) return `${(n / 1000).toFixed(1)} s`;
|
|
385
|
+
const minutes = Math.floor(n / 60_000);
|
|
386
|
+
const seconds = Math.round((n % 60_000) / 1000);
|
|
387
|
+
return `${minutes}m ${seconds}s`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function downloadRunBundle({ record, runId, sourceId } = {}) {
|
|
391
|
+
const normalized = normalizeRunConsoleRecord(record || {});
|
|
392
|
+
return {
|
|
393
|
+
kind: RUN_LOG_BUNDLE_KIND,
|
|
394
|
+
exportedAt: new Date().toISOString(),
|
|
395
|
+
runId: safeString(runId).trim() || (normalized ? normalized.runId : ""),
|
|
396
|
+
sourceId: safeString(sourceId).trim() || (normalized ? normalized.sourceId : ""),
|
|
397
|
+
record: normalized
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export {
|
|
402
|
+
RUN_LOG_BUNDLE_KIND,
|
|
403
|
+
DEFAULT_EXPORT_TARGETS,
|
|
404
|
+
normalizeRunConsoleRecord,
|
|
405
|
+
deriveRunSummary,
|
|
406
|
+
deriveRunLifecycle,
|
|
407
|
+
buildRunLogTree,
|
|
408
|
+
buildRunTimeline,
|
|
409
|
+
filterRunLogTree,
|
|
410
|
+
formatRunDuration,
|
|
411
|
+
downloadRunBundle
|
|
412
|
+
};
|