@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
|
@@ -1,22 +1,376 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo, useState } from "react";
|
|
4
|
-
import { ArrowLeft, GitBranch } from "lucide-react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { ArrowLeft, Download, GitBranch, Play, RefreshCw, Search, Square } from "lucide-react";
|
|
5
5
|
import { parseSandboxRunTrace, normalizeRunRecord } from "@/lib/orchestration-run-trace";
|
|
6
|
+
import {
|
|
7
|
+
buildRunTimeline,
|
|
8
|
+
downloadRunBundle,
|
|
9
|
+
filterRunLogTree,
|
|
10
|
+
formatRunDuration,
|
|
11
|
+
normalizeRunConsoleRecord
|
|
12
|
+
} from "@/lib/orchestration-run-console";
|
|
6
13
|
import { redactSecretsFromText } from "@/lib/orchestration-graph";
|
|
7
14
|
|
|
15
|
+
const ACTIVE_STATUSES = new Set(["executing", "queued", "testing", "running"]);
|
|
16
|
+
const LIVE_POLL_MS = 1500;
|
|
17
|
+
|
|
18
|
+
function formatTimestamp(iso) {
|
|
19
|
+
const text = String(iso || "").trim();
|
|
20
|
+
if (!text) return "—";
|
|
21
|
+
const ms = Date.parse(text);
|
|
22
|
+
if (!Number.isFinite(ms)) return text;
|
|
23
|
+
try {
|
|
24
|
+
return new Date(ms).toLocaleString();
|
|
25
|
+
} catch {
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function statusToVariant(status) {
|
|
31
|
+
switch (status) {
|
|
32
|
+
case "completed":
|
|
33
|
+
return "ok";
|
|
34
|
+
case "failed":
|
|
35
|
+
return "fail";
|
|
36
|
+
case "executing":
|
|
37
|
+
case "queued":
|
|
38
|
+
case "testing":
|
|
39
|
+
case "running":
|
|
40
|
+
return "active";
|
|
41
|
+
case "canceled":
|
|
42
|
+
return "canceled";
|
|
43
|
+
default:
|
|
44
|
+
return "neutral";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function describeStatus(status) {
|
|
49
|
+
switch (status) {
|
|
50
|
+
case "completed":
|
|
51
|
+
return "Completed";
|
|
52
|
+
case "failed":
|
|
53
|
+
return "Failed";
|
|
54
|
+
case "executing":
|
|
55
|
+
return "Executing";
|
|
56
|
+
case "queued":
|
|
57
|
+
return "Queued";
|
|
58
|
+
case "testing":
|
|
59
|
+
return "Testing";
|
|
60
|
+
case "running":
|
|
61
|
+
return "Running";
|
|
62
|
+
case "canceled":
|
|
63
|
+
return "Canceled";
|
|
64
|
+
default:
|
|
65
|
+
return "Unknown";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function previewRecordFromRow(row, rowTrace) {
|
|
70
|
+
const ranAt = rowTrace.ranAt || row?.lastTested || "";
|
|
71
|
+
const previewSource = {
|
|
72
|
+
runId: rowTrace.runId || row?.lastRunId || "",
|
|
73
|
+
ranAt,
|
|
74
|
+
exitCode: rowTrace.exitCode ?? null,
|
|
75
|
+
durationMs: rowTrace.durationMs ?? null,
|
|
76
|
+
error: rowTrace.error || "",
|
|
77
|
+
stdout: rowTrace.stdout || "",
|
|
78
|
+
stderr: rowTrace.stderr || "",
|
|
79
|
+
output: rowTrace.output || "",
|
|
80
|
+
runtime: rowTrace.runtime || row?.runtime || "",
|
|
81
|
+
adapter: rowTrace.adapter || row?.adapter || "",
|
|
82
|
+
runLocality: rowTrace.runLocality || row?.runLocality || "",
|
|
83
|
+
envRefsResolved: rowTrace.envRefsResolved || [],
|
|
84
|
+
envRefsMissing: rowTrace.envRefsMissing || [],
|
|
85
|
+
sourceId: row?.lastSourceId || "",
|
|
86
|
+
version: row?.version || "",
|
|
87
|
+
lifecycleStatus: row?.lifecycleStatus || ""
|
|
88
|
+
};
|
|
89
|
+
return normalizeRunConsoleRecord(previewSource);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function bundleToBlobUrl(bundle) {
|
|
93
|
+
const text = JSON.stringify(bundle, null, 2);
|
|
94
|
+
const blob = new Blob([text], { type: "application/json" });
|
|
95
|
+
return URL.createObjectURL(blob);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function downloadFile(filename, bundle) {
|
|
99
|
+
if (typeof window === "undefined") return;
|
|
100
|
+
const url = bundleToBlobUrl(bundle);
|
|
101
|
+
const link = document.createElement("a");
|
|
102
|
+
link.href = url;
|
|
103
|
+
link.download = filename;
|
|
104
|
+
document.body.appendChild(link);
|
|
105
|
+
link.click();
|
|
106
|
+
link.remove();
|
|
107
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function LogTreeNode({ node, depth, selectedId, onSelect, timelineMax }) {
|
|
111
|
+
const isSelected = String(selectedId) === String(node.id);
|
|
112
|
+
const ratio = timelineMax > 0 && node.durationMs > 0
|
|
113
|
+
? Math.min(1, node.durationMs / timelineMax)
|
|
114
|
+
: 0;
|
|
115
|
+
const variant = statusToVariant(node.status);
|
|
116
|
+
return (
|
|
117
|
+
<div className="dm-run-console__tree-block" data-depth={depth}>
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
className={`dm-run-console__tree-row${isSelected ? " is-active" : ""}`}
|
|
121
|
+
data-variant={variant}
|
|
122
|
+
onClick={() => onSelect(node)}
|
|
123
|
+
title={node.label}
|
|
124
|
+
>
|
|
125
|
+
<span className="dm-run-console__tree-indent" aria-hidden="true" style={{ width: `${depth * 12}px` }} />
|
|
126
|
+
<span className="dm-run-console__tree-dot" data-variant={variant} aria-hidden="true" />
|
|
127
|
+
<span className="dm-run-console__tree-label">{node.label}</span>
|
|
128
|
+
<span className="dm-run-console__tree-meta">{formatRunDuration(node.durationMs)}</span>
|
|
129
|
+
<span className="dm-run-console__tree-bar" aria-hidden="true">
|
|
130
|
+
<span style={{ width: `${Math.round(ratio * 100)}%` }} data-variant={variant} />
|
|
131
|
+
</span>
|
|
132
|
+
</button>
|
|
133
|
+
{Array.isArray(node.children) && node.children.length > 0 ? (
|
|
134
|
+
<div className="dm-run-console__tree-children">
|
|
135
|
+
{node.children.map((child) => (
|
|
136
|
+
<LogTreeNode
|
|
137
|
+
key={child.id}
|
|
138
|
+
node={child}
|
|
139
|
+
depth={depth + 1}
|
|
140
|
+
selectedId={selectedId}
|
|
141
|
+
onSelect={onSelect}
|
|
142
|
+
timelineMax={timelineMax}
|
|
143
|
+
/>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
) : null}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function LifecycleBlock({ lifecycle }) {
|
|
152
|
+
if (!Array.isArray(lifecycle) || lifecycle.length === 0) {
|
|
153
|
+
return <p className="dm-run-console__hint">Lifecycle timestamps not available.</p>;
|
|
154
|
+
}
|
|
155
|
+
return (
|
|
156
|
+
<ol className="dm-run-console__lifecycle">
|
|
157
|
+
{lifecycle.map((step) => (
|
|
158
|
+
<li key={step.label}>
|
|
159
|
+
<span className="dm-run-console__lifecycle-label">{step.label}</span>
|
|
160
|
+
<span className="dm-run-console__lifecycle-at">{formatTimestamp(step.at)}</span>
|
|
161
|
+
{step.durationMs > 0 ? (
|
|
162
|
+
<span className="dm-run-console__lifecycle-dur">{formatRunDuration(step.durationMs)}</span>
|
|
163
|
+
) : null}
|
|
164
|
+
</li>
|
|
165
|
+
))}
|
|
166
|
+
</ol>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function KeyValueBlock({ entries }) {
|
|
171
|
+
const list = entries.filter(([, value]) => value !== "" && value != null);
|
|
172
|
+
if (!list.length) return null;
|
|
173
|
+
return (
|
|
174
|
+
<dl className="dm-run-console__kv">
|
|
175
|
+
{list.map(([label, value]) => (
|
|
176
|
+
<div key={label}>
|
|
177
|
+
<dt>{label}</dt>
|
|
178
|
+
<dd>{value}</dd>
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</dl>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function CodeBlock({ label, body }) {
|
|
186
|
+
if (!body) return null;
|
|
187
|
+
return (
|
|
188
|
+
<div className="dm-run-console__code">
|
|
189
|
+
<span>{label}</span>
|
|
190
|
+
<pre>{body}</pre>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function downloadText(filename, text, mime = "text/plain") {
|
|
196
|
+
if (typeof window === "undefined") return;
|
|
197
|
+
const blob = new Blob([text || ""], { type: mime });
|
|
198
|
+
const url = URL.createObjectURL(blob);
|
|
199
|
+
const link = document.createElement("a");
|
|
200
|
+
link.href = url;
|
|
201
|
+
link.download = filename;
|
|
202
|
+
document.body.appendChild(link);
|
|
203
|
+
link.click();
|
|
204
|
+
link.remove();
|
|
205
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function copyToClipboard(text) {
|
|
209
|
+
if (typeof navigator === "undefined" || !navigator?.clipboard?.writeText) return false;
|
|
210
|
+
try {
|
|
211
|
+
await navigator.clipboard.writeText(text || "");
|
|
212
|
+
return true;
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function formatRewardScore(value) {
|
|
219
|
+
const n = Number(value);
|
|
220
|
+
if (!Number.isFinite(n)) return "—";
|
|
221
|
+
return n.toFixed(2);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function SwarmSection({ swarm }) {
|
|
225
|
+
if (!swarm || typeof swarm !== "object") return null;
|
|
226
|
+
const tasks = Array.isArray(swarm.tasks) ? swarm.tasks : [];
|
|
227
|
+
if (tasks.length === 0 && !swarm.orchestrator?.plan && !swarm.synthesis?.answer) return null;
|
|
228
|
+
const completed = tasks.filter((t) => t?.status === "completed").length;
|
|
229
|
+
const score = swarm.reward ? formatRewardScore(swarm.reward.score) : "—";
|
|
230
|
+
const kind = swarm.reward?.kind || "structural-v1";
|
|
231
|
+
const synthesis = swarm.synthesis || null;
|
|
232
|
+
return (
|
|
233
|
+
<section className="dm-run-console__section">
|
|
234
|
+
<h3>Swarm</h3>
|
|
235
|
+
<p className="dm-swarm-summary__line">
|
|
236
|
+
<span><strong>{completed}/{tasks.length}</strong></span>
|
|
237
|
+
<span>score <strong>{score}</strong></span>
|
|
238
|
+
<span className="dm-swarm-summary__kind" title={swarm.reward?.note || ""}>{kind}</span>
|
|
239
|
+
</p>
|
|
240
|
+
{synthesis?.answer ? (
|
|
241
|
+
<details className="dm-swarm-phase" open>
|
|
242
|
+
<summary>
|
|
243
|
+
synthesizer
|
|
244
|
+
{synthesis.parsedOutcomeScore != null
|
|
245
|
+
? ` · ${Number(synthesis.parsedOutcomeScore).toFixed(2)}`
|
|
246
|
+
: ""}
|
|
247
|
+
</summary>
|
|
248
|
+
<pre>{synthesis.answer}</pre>
|
|
249
|
+
</details>
|
|
250
|
+
) : null}
|
|
251
|
+
</section>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function InputsSection({ payload }) {
|
|
256
|
+
const runInputs = payload?.runInputs;
|
|
257
|
+
const summary = payload?.inputSummary;
|
|
258
|
+
if (!runInputs && (!summary || summary.fieldCount === 0)) {
|
|
259
|
+
return (
|
|
260
|
+
<section className="dm-run-console__section">
|
|
261
|
+
<h3>Inputs</h3>
|
|
262
|
+
<p className="dm-run-console__hint">No manual inputs were submitted for this run.</p>
|
|
263
|
+
</section>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const valueEntries = runInputs && runInputs.values && typeof runInputs.values === "object"
|
|
267
|
+
? Object.entries(runInputs.values)
|
|
268
|
+
: [];
|
|
269
|
+
return (
|
|
270
|
+
<section className="dm-run-console__section">
|
|
271
|
+
<h3>Inputs</h3>
|
|
272
|
+
<KeyValueBlock
|
|
273
|
+
entries={[
|
|
274
|
+
["Source", summary?.source || runInputs?.source || "manual"],
|
|
275
|
+
["Fields", String(summary?.fieldCount ?? valueEntries.length)],
|
|
276
|
+
["Files", String(summary?.fileCount ?? (Array.isArray(runInputs?.files) ? runInputs.files.length : 0))]
|
|
277
|
+
]}
|
|
278
|
+
/>
|
|
279
|
+
{valueEntries.length > 0 ? (
|
|
280
|
+
<dl className="dm-run-console__kv">
|
|
281
|
+
{valueEntries.map(([key, value]) => {
|
|
282
|
+
const isSecretRef = value && typeof value === "object" && "secretRef" in value;
|
|
283
|
+
const display = isSecretRef
|
|
284
|
+
? `secretRef: ${String(value.secretRef || "[redacted]")}`
|
|
285
|
+
: typeof value === "string"
|
|
286
|
+
? value
|
|
287
|
+
: JSON.stringify(value);
|
|
288
|
+
return (
|
|
289
|
+
<div key={key}>
|
|
290
|
+
<dt>{key}</dt>
|
|
291
|
+
<dd>{display}</dd>
|
|
292
|
+
</div>
|
|
293
|
+
);
|
|
294
|
+
})}
|
|
295
|
+
</dl>
|
|
296
|
+
) : null}
|
|
297
|
+
</section>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function ExportActions({ exports, output, record, selectedLogNode }) {
|
|
302
|
+
const available = Array.isArray(exports?.available) ? exports.available : [];
|
|
303
|
+
if (available.length === 0) {
|
|
304
|
+
return <p className="dm-run-console__hint">No exports available for this run.</p>;
|
|
305
|
+
}
|
|
306
|
+
const runLabel = String(record?.runId || "run").replace(/[^a-zA-Z0-9_-]+/g, "-") || "run";
|
|
307
|
+
const [copied, setCopied] = useState("");
|
|
308
|
+
|
|
309
|
+
async function handleCopy() {
|
|
310
|
+
const ok = await copyToClipboard(output?.normalizedOutput || output?.stdout || "");
|
|
311
|
+
setCopied(ok ? "Output copied" : "Clipboard unavailable");
|
|
312
|
+
setTimeout(() => setCopied(""), 1600);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<div className="dm-run-console__exports">
|
|
317
|
+
{available.includes("copy-output") && (
|
|
318
|
+
<button type="button" className="dm-btn-outline" onClick={handleCopy}>Copy output</button>
|
|
319
|
+
)}
|
|
320
|
+
{available.includes("download-stdout") && output?.stdout && (
|
|
321
|
+
<button
|
|
322
|
+
type="button"
|
|
323
|
+
className="dm-btn-outline"
|
|
324
|
+
onClick={() => downloadText(`growthub-run-${runLabel}-stdout.txt`, output.stdout)}
|
|
325
|
+
>Download stdout</button>
|
|
326
|
+
)}
|
|
327
|
+
{available.includes("download-stderr") && output?.stderr && (
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
className="dm-btn-outline"
|
|
331
|
+
onClick={() => downloadText(`growthub-run-${runLabel}-stderr.txt`, output.stderr)}
|
|
332
|
+
>Download stderr</button>
|
|
333
|
+
)}
|
|
334
|
+
{available.includes("download-normalized-output") && output?.normalizedOutput && output.normalizedOutput !== output.stdout && (
|
|
335
|
+
<button
|
|
336
|
+
type="button"
|
|
337
|
+
className="dm-btn-outline"
|
|
338
|
+
onClick={() => downloadText(`growthub-run-${runLabel}-output.json`, output.normalizedOutput, "application/json")}
|
|
339
|
+
>Download output JSON</button>
|
|
340
|
+
)}
|
|
341
|
+
{available.includes("download-log-node") && selectedLogNode?.text && (
|
|
342
|
+
<button
|
|
343
|
+
type="button"
|
|
344
|
+
className="dm-btn-outline"
|
|
345
|
+
onClick={() => downloadText(`growthub-run-${runLabel}-${selectedLogNode.id || "log"}.txt`, selectedLogNode.text)}
|
|
346
|
+
>Download selected log node</button>
|
|
347
|
+
)}
|
|
348
|
+
{copied && <span className="dm-run-console__export-toast">{copied}</span>}
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
8
353
|
export function OrchestrationRunTracePanel({
|
|
9
354
|
row,
|
|
10
355
|
objectId,
|
|
11
356
|
fieldName,
|
|
12
357
|
selectedRunId,
|
|
13
358
|
onBack,
|
|
14
|
-
onOpenGraph
|
|
359
|
+
onOpenGraph,
|
|
360
|
+
onReplay,
|
|
361
|
+
running
|
|
15
362
|
}) {
|
|
16
363
|
const [history, setHistory] = useState([]);
|
|
17
364
|
const [historyMessage, setHistoryMessage] = useState("");
|
|
18
365
|
const [loading, setLoading] = useState(false);
|
|
19
366
|
const [activeRunId, setActiveRunId] = useState(String(selectedRunId || row?.lastRunId || "").trim());
|
|
367
|
+
const [query, setQuery] = useState("");
|
|
368
|
+
const [errorsOnly, setErrorsOnly] = useState(false);
|
|
369
|
+
const [showQueueTime, setShowQueueTime] = useState(false);
|
|
370
|
+
const [activeDetailTab, setActiveDetailTab] = useState("overview");
|
|
371
|
+
const [selectedLogId, setSelectedLogId] = useState("root");
|
|
372
|
+
const [replayPending, setReplayPending] = useState(false);
|
|
373
|
+
const abortRef = useRef(null);
|
|
20
374
|
|
|
21
375
|
const rowTrace = useMemo(() => parseSandboxRunTrace(row?.lastResponse), [row?.lastResponse]);
|
|
22
376
|
|
|
@@ -24,136 +378,444 @@ export function OrchestrationRunTracePanel({
|
|
|
24
378
|
setActiveRunId(String(selectedRunId || row?.lastRunId || "").trim());
|
|
25
379
|
}, [selectedRunId, row?.lastRunId, row?.lastResponse]);
|
|
26
380
|
|
|
27
|
-
|
|
381
|
+
const loadHistory = useCallback(async (signal) => {
|
|
28
382
|
const objectIdValue = String(objectId || "").trim();
|
|
29
383
|
const name = String(row?.Name || "").trim();
|
|
30
384
|
if (!objectIdValue || !name) return;
|
|
31
385
|
setLoading(true);
|
|
32
386
|
setHistoryMessage("");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
387
|
+
try {
|
|
388
|
+
const res = await fetch(
|
|
389
|
+
`/api/workspace/sandbox-run?objectId=${encodeURIComponent(objectIdValue)}&name=${encodeURIComponent(name)}`,
|
|
390
|
+
{ cache: "no-store", signal }
|
|
391
|
+
);
|
|
392
|
+
const payload = await res.json();
|
|
393
|
+
if (!payload.ok) throw new Error(payload.error || "Could not load run history");
|
|
394
|
+
const normalized = Array.isArray(payload.records)
|
|
395
|
+
? payload.records.map(normalizeRunRecord).filter(Boolean)
|
|
396
|
+
: [];
|
|
397
|
+
setHistory(normalized);
|
|
398
|
+
setHistoryMessage(`${payload.recordCount || normalized.length} saved run${(payload.recordCount || normalized.length) === 1 ? "" : "s"}`);
|
|
399
|
+
} catch (err) {
|
|
400
|
+
if (err?.name === "AbortError") return;
|
|
401
|
+
setHistory([]);
|
|
402
|
+
setHistoryMessage(err.message || "Could not load run history");
|
|
403
|
+
} finally {
|
|
404
|
+
setLoading(false);
|
|
405
|
+
}
|
|
47
406
|
}, [objectId, row?.Name]);
|
|
48
407
|
|
|
49
|
-
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
const controller = new AbortController();
|
|
410
|
+
loadHistory(controller.signal);
|
|
411
|
+
return () => controller.abort();
|
|
412
|
+
}, [loadHistory]);
|
|
413
|
+
|
|
414
|
+
const isClientRunning = Boolean(running) || replayPending;
|
|
415
|
+
const liveReloading = isClientRunning;
|
|
416
|
+
|
|
417
|
+
useEffect(() => {
|
|
418
|
+
if (!liveReloading) return undefined;
|
|
419
|
+
const id = setInterval(() => {
|
|
420
|
+
loadHistory();
|
|
421
|
+
}, LIVE_POLL_MS);
|
|
422
|
+
return () => clearInterval(id);
|
|
423
|
+
}, [liveReloading, loadHistory]);
|
|
424
|
+
|
|
425
|
+
const activeRawRecord = useMemo(() => {
|
|
50
426
|
if (activeRunId && history.length) {
|
|
51
427
|
const match = history.find((r) => r.runId === activeRunId);
|
|
52
428
|
if (match) return match;
|
|
53
429
|
}
|
|
54
430
|
if (fieldName === "lastResponse" || !activeRunId) {
|
|
55
|
-
return
|
|
56
|
-
runId: rowTrace.runId || row?.lastRunId || "",
|
|
57
|
-
ranAt: rowTrace.ranAt || row?.lastTested || "",
|
|
58
|
-
exitCode: rowTrace.exitCode,
|
|
59
|
-
durationMs: rowTrace.durationMs,
|
|
60
|
-
error: rowTrace.error,
|
|
61
|
-
stdout: rowTrace.stdout,
|
|
62
|
-
stderr: rowTrace.stderr,
|
|
63
|
-
output: rowTrace.output
|
|
64
|
-
};
|
|
431
|
+
return null;
|
|
65
432
|
}
|
|
66
433
|
return history[0] || null;
|
|
67
|
-
}, [activeRunId, history,
|
|
434
|
+
}, [activeRunId, history, fieldName]);
|
|
435
|
+
|
|
436
|
+
const activeConsoleRecord = useMemo(() => {
|
|
437
|
+
if (activeRawRecord) return normalizeRunConsoleRecord(activeRawRecord);
|
|
438
|
+
return previewRecordFromRow(row, rowTrace);
|
|
439
|
+
}, [activeRawRecord, row, rowTrace]);
|
|
68
440
|
|
|
69
|
-
const
|
|
441
|
+
const timeline = useMemo(() => buildRunTimeline(history), [history]);
|
|
442
|
+
const timelineMax = timeline.reduce((m, item) => Math.max(m, item.durationMs || 0), 0);
|
|
443
|
+
|
|
444
|
+
const filteredTree = useMemo(() => (
|
|
445
|
+
filterRunLogTree(activeConsoleRecord?.logTree || [], { query, errorsOnly })
|
|
446
|
+
), [activeConsoleRecord, query, errorsOnly]);
|
|
447
|
+
|
|
448
|
+
const selectedLogNode = useMemo(() => {
|
|
449
|
+
const tree = activeConsoleRecord?.logTree || [];
|
|
450
|
+
const stack = [...tree];
|
|
451
|
+
while (stack.length) {
|
|
452
|
+
const node = stack.pop();
|
|
453
|
+
if (!node) continue;
|
|
454
|
+
if (String(node.id) === String(selectedLogId)) return node;
|
|
455
|
+
if (Array.isArray(node.children)) stack.push(...node.children);
|
|
456
|
+
}
|
|
457
|
+
return tree[0] || null;
|
|
458
|
+
}, [activeConsoleRecord, selectedLogId]);
|
|
459
|
+
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
setSelectedLogId("root");
|
|
462
|
+
}, [activeRunId]);
|
|
463
|
+
|
|
464
|
+
const summaryText = fieldName === "lastSourceId"
|
|
70
465
|
? `Source ${String(row?.lastSourceId || "").trim()}`
|
|
71
466
|
: fieldName === "lastRunId"
|
|
72
467
|
? `Run ${String(row?.lastRunId || activeRunId || "").trim()}`
|
|
73
468
|
: "Latest sandbox run";
|
|
74
469
|
|
|
470
|
+
const statusVariant = statusToVariant(activeConsoleRecord?.status);
|
|
471
|
+
const statusLabel = describeStatus(activeConsoleRecord?.status);
|
|
472
|
+
|
|
473
|
+
const canReplay = typeof onReplay === "function";
|
|
474
|
+
|
|
475
|
+
function handleReplay() {
|
|
476
|
+
if (!canReplay) return;
|
|
477
|
+
setReplayPending(true);
|
|
478
|
+
const controller = new AbortController();
|
|
479
|
+
abortRef.current = controller;
|
|
480
|
+
try {
|
|
481
|
+
const result = onReplay({ signal: controller.signal });
|
|
482
|
+
Promise.resolve(result)
|
|
483
|
+
.catch(() => {})
|
|
484
|
+
.finally(() => {
|
|
485
|
+
setReplayPending(false);
|
|
486
|
+
abortRef.current = null;
|
|
487
|
+
loadHistory();
|
|
488
|
+
});
|
|
489
|
+
} catch {
|
|
490
|
+
setReplayPending(false);
|
|
491
|
+
abortRef.current = null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function handleCancel() {
|
|
496
|
+
if (abortRef.current) {
|
|
497
|
+
try { abortRef.current.abort(); } catch { /* noop */ }
|
|
498
|
+
}
|
|
499
|
+
setReplayPending(false);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function handleDownload() {
|
|
503
|
+
if (!activeConsoleRecord) return;
|
|
504
|
+
const bundle = downloadRunBundle({
|
|
505
|
+
record: activeRawRecord || activeConsoleRecord,
|
|
506
|
+
runId: activeConsoleRecord.runId,
|
|
507
|
+
sourceId: activeConsoleRecord.sourceId || row?.lastSourceId || ""
|
|
508
|
+
});
|
|
509
|
+
const runLabel = String(bundle.runId || "run").replace(/[^a-zA-Z0-9_-]+/g, "-") || "run";
|
|
510
|
+
downloadFile(`growthub-run-${runLabel}.json`, bundle);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const lifecycleEntries = activeConsoleRecord?.lifecycle || [];
|
|
514
|
+
const payload = activeConsoleRecord?.payload || {};
|
|
515
|
+
const output = activeConsoleRecord?.output || {};
|
|
516
|
+
const context = activeConsoleRecord?.context || {};
|
|
517
|
+
const swarmPayload = activeConsoleRecord?.swarm
|
|
518
|
+
|| (activeRawRecord && activeRawRecord.swarm)
|
|
519
|
+
|| null;
|
|
520
|
+
|
|
75
521
|
return (
|
|
76
|
-
<section className="dm-
|
|
77
|
-
<header className="dm-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
<div>
|
|
82
|
-
<h2>Run trace</h2>
|
|
83
|
-
<p>{summary} · {row?.Name || "Sandbox tool"}</p>
|
|
84
|
-
</div>
|
|
85
|
-
{onOpenGraph && (
|
|
86
|
-
<button type="button" className="dm-btn-outline" onClick={onOpenGraph}>
|
|
87
|
-
<GitBranch size={14} aria-hidden="true" />
|
|
88
|
-
Edit graph
|
|
522
|
+
<section className="dm-run-console" aria-label="Live runs console">
|
|
523
|
+
<header className="dm-run-console__head">
|
|
524
|
+
{onBack && (
|
|
525
|
+
<button type="button" className="dm-orchestration-header__back" onClick={onBack} aria-label="Back to record">
|
|
526
|
+
<ArrowLeft size={16} />
|
|
89
527
|
</button>
|
|
90
528
|
)}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
{
|
|
97
|
-
|
|
529
|
+
<div className="dm-run-console__head-titles">
|
|
530
|
+
<span className="dm-run-console__crumbs">
|
|
531
|
+
Runs <span aria-hidden="true">/</span> <code>{activeConsoleRecord?.runId || "preview"}</code>
|
|
532
|
+
</span>
|
|
533
|
+
<h2>Run console</h2>
|
|
534
|
+
<p>{summaryText} · {row?.Name || "Sandbox tool"}</p>
|
|
535
|
+
</div>
|
|
536
|
+
<div className="dm-run-console__head-actions">
|
|
537
|
+
{canReplay && (
|
|
538
|
+
<button
|
|
539
|
+
type="button"
|
|
540
|
+
className="dm-btn-outline"
|
|
541
|
+
onClick={handleReplay}
|
|
542
|
+
disabled={isClientRunning}
|
|
543
|
+
title="Replay current saved config"
|
|
544
|
+
>
|
|
545
|
+
<Play size={13} aria-hidden="true" />
|
|
546
|
+
{replayPending ? "Replaying" : "Replay current config"}
|
|
547
|
+
</button>
|
|
548
|
+
)}
|
|
549
|
+
{isClientRunning && (
|
|
550
|
+
<button
|
|
551
|
+
type="button"
|
|
552
|
+
className="dm-btn-outline dm-run-console__cancel"
|
|
553
|
+
onClick={handleCancel}
|
|
554
|
+
title="Cancel the in-flight client request"
|
|
555
|
+
>
|
|
556
|
+
<Square size={13} aria-hidden="true" />
|
|
557
|
+
Cancel request
|
|
558
|
+
</button>
|
|
559
|
+
)}
|
|
98
560
|
<button
|
|
99
561
|
type="button"
|
|
100
|
-
className=
|
|
101
|
-
onClick={
|
|
562
|
+
className="dm-btn-outline"
|
|
563
|
+
onClick={handleDownload}
|
|
564
|
+
disabled={!activeConsoleRecord}
|
|
565
|
+
title="Download redacted JSON log for this run"
|
|
102
566
|
>
|
|
103
|
-
<
|
|
104
|
-
|
|
567
|
+
<Download size={13} aria-hidden="true" />
|
|
568
|
+
Download logs
|
|
105
569
|
</button>
|
|
106
|
-
{
|
|
570
|
+
{onOpenGraph && (
|
|
571
|
+
<button type="button" className="dm-btn-outline" onClick={onOpenGraph}>
|
|
572
|
+
<GitBranch size={13} aria-hidden="true" />
|
|
573
|
+
Edit graph
|
|
574
|
+
</button>
|
|
575
|
+
)}
|
|
576
|
+
</div>
|
|
577
|
+
</header>
|
|
578
|
+
|
|
579
|
+
<div className="dm-run-console__split">
|
|
580
|
+
<aside className="dm-run-console__left">
|
|
581
|
+
<div className="dm-run-console__toolbar">
|
|
582
|
+
<label className="dm-run-console__search">
|
|
583
|
+
<Search size={12} aria-hidden="true" />
|
|
584
|
+
<input
|
|
585
|
+
type="search"
|
|
586
|
+
placeholder="Search log"
|
|
587
|
+
value={query}
|
|
588
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
589
|
+
aria-label="Search log"
|
|
590
|
+
/>
|
|
591
|
+
</label>
|
|
592
|
+
<label className="dm-run-console__toggle">
|
|
593
|
+
<input
|
|
594
|
+
type="checkbox"
|
|
595
|
+
checked={showQueueTime}
|
|
596
|
+
onChange={(e) => setShowQueueTime(e.target.checked)}
|
|
597
|
+
/>
|
|
598
|
+
<span>Queue time</span>
|
|
599
|
+
</label>
|
|
600
|
+
<label className="dm-run-console__toggle">
|
|
601
|
+
<input
|
|
602
|
+
type="checkbox"
|
|
603
|
+
checked={errorsOnly}
|
|
604
|
+
onChange={(e) => setErrorsOnly(e.target.checked)}
|
|
605
|
+
/>
|
|
606
|
+
<span>Errors only</span>
|
|
607
|
+
</label>
|
|
608
|
+
<span
|
|
609
|
+
className={`dm-run-console__live${liveReloading ? " is-on" : ""}`}
|
|
610
|
+
aria-live="polite"
|
|
611
|
+
>
|
|
612
|
+
<span className="dm-run-console__live-dot" aria-hidden="true" />
|
|
613
|
+
{liveReloading ? "Live reloading" : "Idle"}
|
|
614
|
+
</span>
|
|
615
|
+
</div>
|
|
616
|
+
|
|
617
|
+
<div className="dm-run-console__history">
|
|
618
|
+
<p className="dm-run-console__history-title">Run history</p>
|
|
619
|
+
{loading && <p className="dm-run-console__hint">Loading…</p>}
|
|
620
|
+
{!loading && historyMessage && <p className="dm-run-console__hint">{historyMessage}</p>}
|
|
107
621
|
<button
|
|
108
|
-
key={record.runId || record.ranAt}
|
|
109
622
|
type="button"
|
|
110
|
-
className={`dm-
|
|
111
|
-
onClick={() => setActiveRunId(
|
|
623
|
+
className={`dm-run-console__history-row${!activeRunId ? " is-active" : ""}`}
|
|
624
|
+
onClick={() => setActiveRunId("")}
|
|
112
625
|
>
|
|
113
|
-
<span
|
|
114
|
-
<span>
|
|
115
|
-
{
|
|
116
|
-
{record.ranAt ? ` · ${record.ranAt}` : ""}
|
|
626
|
+
<span className="dm-run-console__history-label">Row preview</span>
|
|
627
|
+
<span className="dm-run-console__history-meta">
|
|
628
|
+
{row?.status || rowTrace.status || "—"} · {formatTimestamp(row?.lastTested || rowTrace.ranAt)}
|
|
117
629
|
</span>
|
|
118
630
|
</button>
|
|
119
|
-
|
|
120
|
-
|
|
631
|
+
{history.map((record) => {
|
|
632
|
+
const isActive = activeRunId === record.runId;
|
|
633
|
+
const variant = statusToVariant(normalizeRunConsoleRecord(record)?.status);
|
|
634
|
+
return (
|
|
635
|
+
<button
|
|
636
|
+
key={record.runId || record.ranAt}
|
|
637
|
+
type="button"
|
|
638
|
+
className={`dm-run-console__history-row${isActive ? " is-active" : ""}`}
|
|
639
|
+
data-variant={variant}
|
|
640
|
+
onClick={() => setActiveRunId(record.runId)}
|
|
641
|
+
>
|
|
642
|
+
<span className="dm-run-console__history-label">
|
|
643
|
+
<span className="dm-run-console__tree-dot" data-variant={variant} aria-hidden="true" />
|
|
644
|
+
{record.runId || "run"}
|
|
645
|
+
</span>
|
|
646
|
+
<span className="dm-run-console__history-meta">
|
|
647
|
+
{record.exitCode === 0 && !record.error ? "completed" : "failed"} · {formatTimestamp(record.ranAt)}
|
|
648
|
+
</span>
|
|
649
|
+
</button>
|
|
650
|
+
);
|
|
651
|
+
})}
|
|
652
|
+
</div>
|
|
653
|
+
|
|
654
|
+
<div className="dm-run-console__tree">
|
|
655
|
+
<p className="dm-run-console__history-title">Log tree</p>
|
|
656
|
+
{filteredTree.length === 0 && (
|
|
657
|
+
<p className="dm-run-console__hint">
|
|
658
|
+
{errorsOnly ? "No error entries in this run." : "No log entries yet."}
|
|
659
|
+
</p>
|
|
660
|
+
)}
|
|
661
|
+
{filteredTree.map((node) => (
|
|
662
|
+
<LogTreeNode
|
|
663
|
+
key={node.id}
|
|
664
|
+
node={node}
|
|
665
|
+
depth={0}
|
|
666
|
+
selectedId={selectedLogNode?.id}
|
|
667
|
+
onSelect={(n) => setSelectedLogId(n.id)}
|
|
668
|
+
timelineMax={timelineMax}
|
|
669
|
+
/>
|
|
670
|
+
))}
|
|
671
|
+
</div>
|
|
121
672
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<pre>{redactSecretsFromText(activeRecord?.error || rowTrace.error)}</pre>
|
|
673
|
+
{showQueueTime && timeline.length > 0 && (
|
|
674
|
+
<div className="dm-run-console__timeline">
|
|
675
|
+
<p className="dm-run-console__history-title">Timeline</p>
|
|
676
|
+
{timeline.map((item) => {
|
|
677
|
+
const variant = statusToVariant(item.status);
|
|
678
|
+
return (
|
|
679
|
+
<div key={item.runId || item.ranAt} className="dm-run-console__timeline-row" data-variant={variant}>
|
|
680
|
+
<span className="dm-run-console__timeline-label" title={item.runId}>{item.runId || "run"}</span>
|
|
681
|
+
<span className="dm-run-console__timeline-bar" aria-hidden="true">
|
|
682
|
+
<span style={{ width: `${Math.round((item.barRatio || 0) * 100)}%` }} data-variant={variant} />
|
|
683
|
+
</span>
|
|
684
|
+
<span className="dm-run-console__timeline-dur">{formatRunDuration(item.durationMs)}</span>
|
|
685
|
+
</div>
|
|
686
|
+
);
|
|
687
|
+
})}
|
|
138
688
|
</div>
|
|
139
689
|
)}
|
|
690
|
+
</aside>
|
|
140
691
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
692
|
+
<div className="dm-run-console__right">
|
|
693
|
+
<div className="dm-run-console__detail-head">
|
|
694
|
+
<div className="dm-run-console__detail-title">
|
|
695
|
+
<span className={`dm-run-console__status-pill is-${statusVariant}`}>{statusLabel}</span>
|
|
696
|
+
<strong>{selectedLogNode?.label || "agent-run"}</strong>
|
|
697
|
+
<small>{activeConsoleRecord?.runId || "—"}</small>
|
|
698
|
+
</div>
|
|
699
|
+
<div className="dm-run-console__detail-tabs" role="tablist">
|
|
700
|
+
{["overview", "detail", "context"].map((tab) => (
|
|
701
|
+
<button
|
|
702
|
+
key={tab}
|
|
703
|
+
type="button"
|
|
704
|
+
role="tab"
|
|
705
|
+
className={`dm-run-console__detail-tab${activeDetailTab === tab ? " is-active" : ""}`}
|
|
706
|
+
onClick={() => setActiveDetailTab(tab)}
|
|
707
|
+
>
|
|
708
|
+
{tab === "overview" ? "Overview" : tab === "detail" ? "Detail" : "Context"}
|
|
709
|
+
</button>
|
|
710
|
+
))}
|
|
711
|
+
</div>
|
|
144
712
|
</div>
|
|
145
713
|
|
|
146
|
-
{
|
|
147
|
-
<div className="dm-
|
|
148
|
-
<
|
|
149
|
-
|
|
714
|
+
{activeDetailTab === "overview" && (
|
|
715
|
+
<div className="dm-run-console__detail-body">
|
|
716
|
+
<section className="dm-run-console__section">
|
|
717
|
+
<h3>Lifecycle</h3>
|
|
718
|
+
<LifecycleBlock lifecycle={lifecycleEntries} />
|
|
719
|
+
</section>
|
|
720
|
+
<section className="dm-run-console__section">
|
|
721
|
+
<h3>Summary</h3>
|
|
722
|
+
<KeyValueBlock
|
|
723
|
+
entries={[
|
|
724
|
+
["Status", statusLabel],
|
|
725
|
+
["Run ID", activeConsoleRecord?.runId || "—"],
|
|
726
|
+
["Exit code", activeConsoleRecord?.exitCode == null ? "—" : String(activeConsoleRecord.exitCode)],
|
|
727
|
+
["Duration", formatRunDuration(activeConsoleRecord?.durationMs)],
|
|
728
|
+
["Runtime", activeConsoleRecord?.runtime || "—"],
|
|
729
|
+
["Adapter", activeConsoleRecord?.adapter || "—"],
|
|
730
|
+
["Run locality", activeConsoleRecord?.runLocality || "—"],
|
|
731
|
+
["Tested", formatTimestamp(activeConsoleRecord?.ranAt)],
|
|
732
|
+
["Finished", formatTimestamp(activeConsoleRecord?.finishedAt)]
|
|
733
|
+
]}
|
|
734
|
+
/>
|
|
735
|
+
</section>
|
|
736
|
+
<section className="dm-run-console__section">
|
|
737
|
+
<h3>Payload</h3>
|
|
738
|
+
<KeyValueBlock
|
|
739
|
+
entries={[
|
|
740
|
+
["Object", payload.objectId || "—"],
|
|
741
|
+
["Name", payload.name || row?.Name || "—"],
|
|
742
|
+
["Runtime", payload.runtime || "—"],
|
|
743
|
+
["Adapter", payload.adapter || "—"],
|
|
744
|
+
["Version", payload.version || "—"],
|
|
745
|
+
["Agent host", payload.agentHost || "—"],
|
|
746
|
+
["Scheduler", payload.schedulerRegistryId || "—"],
|
|
747
|
+
["Timeout", payload.timeoutMs ? `${payload.timeoutMs} ms` : "—"]
|
|
748
|
+
]}
|
|
749
|
+
/>
|
|
750
|
+
<CodeBlock label="Command" body={payload.command} />
|
|
751
|
+
<CodeBlock label="Instructions" body={payload.instructions} />
|
|
752
|
+
</section>
|
|
753
|
+
<SwarmSection swarm={swarmPayload} />
|
|
754
|
+
<InputsSection payload={payload} />
|
|
755
|
+
</div>
|
|
756
|
+
)}
|
|
757
|
+
|
|
758
|
+
{activeDetailTab === "detail" && (
|
|
759
|
+
<div className="dm-run-console__detail-body">
|
|
760
|
+
<section className="dm-run-console__section">
|
|
761
|
+
<h3>Export</h3>
|
|
762
|
+
<ExportActions
|
|
763
|
+
exports={activeConsoleRecord?.exports}
|
|
764
|
+
output={output}
|
|
765
|
+
record={activeConsoleRecord}
|
|
766
|
+
selectedLogNode={selectedLogNode}
|
|
767
|
+
/>
|
|
768
|
+
</section>
|
|
769
|
+
<section className="dm-run-console__section">
|
|
770
|
+
<h3>Output</h3>
|
|
771
|
+
<CodeBlock label="Error" body={output.error} />
|
|
772
|
+
<CodeBlock label="Stdout" body={output.stdout || "—"} />
|
|
773
|
+
{output.normalizedOutput && output.normalizedOutput !== output.stdout && (
|
|
774
|
+
<CodeBlock label="Normalized output" body={output.normalizedOutput} />
|
|
775
|
+
)}
|
|
776
|
+
<CodeBlock label="Stderr" body={output.stderr} />
|
|
777
|
+
</section>
|
|
778
|
+
{selectedLogNode?.text ? (
|
|
779
|
+
<section className="dm-run-console__section">
|
|
780
|
+
<h3>{selectedLogNode.label}</h3>
|
|
781
|
+
<CodeBlock label={selectedLogNode.type || "log"} body={selectedLogNode.text} />
|
|
782
|
+
</section>
|
|
783
|
+
) : null}
|
|
150
784
|
</div>
|
|
151
785
|
)}
|
|
152
786
|
|
|
153
|
-
{
|
|
154
|
-
<div className="dm-
|
|
155
|
-
<
|
|
156
|
-
|
|
787
|
+
{activeDetailTab === "context" && (
|
|
788
|
+
<div className="dm-run-console__detail-body">
|
|
789
|
+
<section className="dm-run-console__section">
|
|
790
|
+
<h3>Environment</h3>
|
|
791
|
+
<KeyValueBlock
|
|
792
|
+
entries={[
|
|
793
|
+
["Network allow", context.networkAllow ? "true" : "false"],
|
|
794
|
+
["Allow list", context.allowList?.join(", ") || "—"],
|
|
795
|
+
["Env refs resolved", context.envRefsResolved?.join(", ") || "—"],
|
|
796
|
+
["Env refs missing", context.envRefsMissing?.join(", ") || "—"],
|
|
797
|
+
["Source ID", activeConsoleRecord?.sourceId || row?.lastSourceId || "—"]
|
|
798
|
+
]}
|
|
799
|
+
/>
|
|
800
|
+
</section>
|
|
801
|
+
{context.adapterMeta && (
|
|
802
|
+
<section className="dm-run-console__section">
|
|
803
|
+
<h3>Adapter metadata</h3>
|
|
804
|
+
<CodeBlock
|
|
805
|
+
label="adapterMeta"
|
|
806
|
+
body={redactSecretsFromText(JSON.stringify(context.adapterMeta, null, 2))}
|
|
807
|
+
/>
|
|
808
|
+
</section>
|
|
809
|
+
)}
|
|
810
|
+
{context.templateTrace && (
|
|
811
|
+
<section className="dm-run-console__section">
|
|
812
|
+
<h3>Template trace</h3>
|
|
813
|
+
<CodeBlock
|
|
814
|
+
label="templateTrace"
|
|
815
|
+
body={redactSecretsFromText(JSON.stringify(context.templateTrace, null, 2))}
|
|
816
|
+
/>
|
|
817
|
+
</section>
|
|
818
|
+
)}
|
|
157
819
|
</div>
|
|
158
820
|
)}
|
|
159
821
|
</div>
|