@smithers-orchestrator/cli 0.20.1 → 0.20.3

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.
Files changed (33) hide show
  1. package/package.json +17 -19
  2. package/src/index.js +52 -44
  3. package/src/workflow-pack.js +15 -6
  4. package/src/tui/app.jsx +0 -139
  5. package/src/tui/app.tsx +0 -5
  6. package/src/tui/components/AskModal.jsx +0 -109
  7. package/src/tui/components/AskModal.tsx +0 -3
  8. package/src/tui/components/AttentionPane.jsx +0 -112
  9. package/src/tui/components/AttentionPane.tsx +0 -6
  10. package/src/tui/components/ChatPane.jsx +0 -57
  11. package/src/tui/components/ChatPane.tsx +0 -7
  12. package/src/tui/components/CronList.jsx +0 -87
  13. package/src/tui/components/CronList.tsx +0 -5
  14. package/src/tui/components/DetailsPane.jsx +0 -96
  15. package/src/tui/components/DetailsPane.tsx +0 -7
  16. package/src/tui/components/FramesPane.jsx +0 -147
  17. package/src/tui/components/FramesPane.tsx +0 -8
  18. package/src/tui/components/LogsPane.jsx +0 -46
  19. package/src/tui/components/LogsPane.tsx +0 -6
  20. package/src/tui/components/MetricsPane.jsx +0 -108
  21. package/src/tui/components/MetricsPane.tsx +0 -5
  22. package/src/tui/components/NodeDetailView.jsx +0 -284
  23. package/src/tui/components/NodeDetailView.tsx +0 -7
  24. package/src/tui/components/NodeInspector.jsx +0 -51
  25. package/src/tui/components/NodeInspector.tsx +0 -7
  26. package/src/tui/components/RunDetailView.jsx +0 -190
  27. package/src/tui/components/RunDetailView.tsx +0 -7
  28. package/src/tui/components/RunsList.jsx +0 -184
  29. package/src/tui/components/RunsList.tsx +0 -7
  30. package/src/tui/components/SqliteBrowser.jsx +0 -131
  31. package/src/tui/components/SqliteBrowser.tsx +0 -5
  32. package/src/tui/components/WorkflowLauncher.jsx +0 -63
  33. package/src/tui/components/WorkflowLauncher.tsx +0 -3
@@ -1,284 +0,0 @@
1
- // @ts-nocheck
2
- import React, { useEffect, useState } from "react";
3
- import { useKeyboard } from "@opentui/react";
4
- import { ChatPane } from "./ChatPane.jsx";
5
- import { LogsPane } from "./LogsPane.jsx";
6
- import { FramesPane } from "./FramesPane.jsx";
7
- /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} SmithersDb */
8
-
9
- /**
10
- * @param {{ adapter: SmithersDb; runId: string; nodeId: string | null; // null means "Global Run" onBack: () => void; }} value
11
- */
12
- export function NodeDetailView({ adapter, runId, nodeId, onBack, }) {
13
- const [runData, setRunData] = useState(null);
14
- const [nodeData, setNodeData] = useState(null);
15
- const [attempts, setAttempts] = useState([]);
16
- const [events, setEvents] = useState([]);
17
- const [rawOutput, setRawOutput] = useState(null);
18
- const [scorerResults, setScorerResults] = useState([]);
19
- const [tab, setTab] = useState("output");
20
- useEffect(() => {
21
- let mounted = true;
22
- let timeout;
23
- async function fetchDetails() {
24
- if (!mounted)
25
- return;
26
- try {
27
- const run = await adapter.getRun(runId);
28
- const fetchedAttempts = await adapter.listAttemptsForRun(runId);
29
- const fetchedEvents = await adapter.listEvents(runId, -1, 10000);
30
- let fetchedNode = null;
31
- let fetchedRawOutput = null;
32
- if (nodeId) {
33
- const nodes = await adapter.listNodes(runId);
34
- fetchedNode = nodes.find(n => n.nodeId === nodeId) || null;
35
- if (fetchedNode?.outputTable) {
36
- fetchedRawOutput = await adapter.getRawNodeOutput(fetchedNode.outputTable, runId, nodeId);
37
- }
38
- }
39
- let fetchedScores = [];
40
- try {
41
- fetchedScores = await adapter.listScorerResults(runId, nodeId ?? undefined);
42
- }
43
- catch { }
44
- if (mounted) {
45
- setRunData(run);
46
- setNodeData(fetchedNode);
47
- setAttempts(fetchedAttempts);
48
- setEvents(fetchedEvents);
49
- setRawOutput(fetchedRawOutput);
50
- setScorerResults(fetchedScores ?? []);
51
- }
52
- }
53
- catch (err) { }
54
- if (mounted)
55
- timeout = setTimeout(fetchDetails, 1000);
56
- }
57
- fetchDetails();
58
- return () => {
59
- mounted = false;
60
- clearTimeout(timeout);
61
- };
62
- }, [adapter, runId, nodeId]);
63
- const isGlobal = nodeId === null;
64
- useKeyboard((key) => {
65
- if (key.name === "escape" || (key.name === "c" && key.ctrl) || key.name === "backspace") {
66
- onBack();
67
- return;
68
- }
69
- if (key.name === "r" && !isGlobal && latestAttempt) {
70
- Bun.spawn([
71
- "bun", "run", "src/index.js", "revert",
72
- "--runId", runId,
73
- "--nodeId", nodeId,
74
- "--attempt", latestAttempt.attempt.toString(),
75
- "--iteration", latestAttempt.iteration.toString()
76
- ], { stdout: "ignore", stderr: "ignore" }).unref();
77
- return;
78
- }
79
- const tabList = ["input", "output", "frames", "chat", "logs", "scores"];
80
- if (key.name === "left" || key.name === "h") {
81
- setTab((prev) => tabList[(tabList.indexOf(prev) - 1 + tabList.length) % tabList.length]);
82
- return;
83
- }
84
- if (key.name === "right" || key.name === "l") {
85
- setTab((prev) => tabList[(tabList.indexOf(prev) + 1) % tabList.length]);
86
- return;
87
- }
88
- if (key.name === "1")
89
- setTab("input");
90
- if (key.name === "2")
91
- setTab("output");
92
- if (key.name === "3")
93
- setTab("frames");
94
- if (key.name === "4")
95
- setTab("chat");
96
- if (key.name === "5")
97
- setTab("logs");
98
- if (key.name === "6")
99
- setTab("scores");
100
- });
101
- if (!runData) {
102
- return <text style={{ margin: 1 }}>Loading inspection data...</text>;
103
- }
104
- /**
105
- * @param {string} [jsonStr]
106
- */
107
- function safePretty(jsonStr) {
108
- if (!jsonStr)
109
- return "None";
110
- try {
111
- return JSON.stringify(JSON.parse(jsonStr), null, 2);
112
- }
113
- catch {
114
- return jsonStr.substring(0, 10000); // RAW String output
115
- }
116
- }
117
- const targetAttempts = isGlobal ? [] : attempts.filter((a) => a.nodeId === nodeId).sort((a, b) => b.attempt - a.attempt);
118
- const latestAttempt = targetAttempts.length > 0 ? targetAttempts[0] : null;
119
- let inputData = isGlobal ? runData.configJson : "Inputs are dynamically constructed. No static properties were captured for this task frame.";
120
- if (!isGlobal && latestAttempt?.metaJson) {
121
- try {
122
- const meta = JSON.parse(latestAttempt.metaJson);
123
- const { inputPrompt, systemPrompt, agentId, model, config, approvalMode, ...rest } = meta;
124
- let str = "[ Agent Configuration ]\n";
125
- if (agentId || model)
126
- str += `Agent: ${agentId ?? "unknown"} | Model: ${model ?? "default"}\n`;
127
- if (approvalMode)
128
- str += `Approval Mode: ${approvalMode}\n`;
129
- if (config) {
130
- try {
131
- str += `Config: ${JSON.stringify(config)}\n`;
132
- }
133
- catch {
134
- str += `Config: ${String(config)}\n`;
135
- }
136
- }
137
- if (systemPrompt)
138
- str += `\n[ System Prompt ]\n${systemPrompt}\n`;
139
- if (inputPrompt)
140
- str += `\n[ Input Prompt ]\n${inputPrompt}\n`;
141
- if (Object.keys(rest).length)
142
- str += `\n[ Other Meta Options ]\n${JSON.stringify(rest, null, 2)}`;
143
- inputData = str;
144
- }
145
- catch (err) {
146
- inputData = `Failed to parse metadata: ${err.message}\nRaw JSON:\n${latestAttempt.metaJson}`;
147
- }
148
- }
149
- let outputData = "No output text available yet.";
150
- if (isGlobal) {
151
- if (runData.errorJson) {
152
- outputData = `ERROR:\n${runData.errorJson}`;
153
- }
154
- else if (runData.status === "finished") {
155
- outputData = "Run completed successfully (no global errorJson stacktrace was captured).\n\nPress [Enter] to inspect individual task payloads.";
156
- }
157
- else if (runData.status === "failed") {
158
- outputData = "Workflow failed (no global errorJson stacktrace was captured).\n\nPress [Enter] to inspect and determine which individual task node crashed.";
159
- }
160
- else {
161
- outputData = "Workflow is still active or pending execution...";
162
- }
163
- }
164
- else {
165
- if (latestAttempt?.errorJson) {
166
- outputData = `ERROR:\n${latestAttempt.errorJson}`;
167
- }
168
- else if (rawOutput) {
169
- try {
170
- const cleanOutput = { ...rawOutput };
171
- delete cleanOutput.run_id;
172
- delete cleanOutput.node_id;
173
- delete cleanOutput.iteration;
174
- // Attempt to parse internal stringified JSON fields for display
175
- for (const [k, v] of Object.entries(cleanOutput)) {
176
- if (typeof v === "string" && (v.startsWith("{") || v.startsWith("["))) {
177
- try {
178
- cleanOutput[k] = JSON.parse(v);
179
- }
180
- catch { }
181
- }
182
- }
183
- outputData = JSON.stringify(cleanOutput);
184
- }
185
- catch {
186
- outputData = JSON.stringify(rawOutput);
187
- }
188
- }
189
- else {
190
- outputData = latestAttempt?.responseText ?? "No output text available yet.";
191
- }
192
- }
193
- let tokensStr = "";
194
- if (!isGlobal && nodeId) {
195
- const usageEvent = events.find((e) => e.type === "TokenUsageReported" && e.nodeId === nodeId && e.attempt === latestAttempt?.attempt);
196
- if (usageEvent) {
197
- try {
198
- const payload = JSON.parse(usageEvent.payloadJson);
199
- tokensStr = ` \n Tokens: ${payload.inputTokens} IN | ${payload.outputTokens} OUT | ${payload.cacheReadTokens ?? 0} CACHE`;
200
- }
201
- catch { }
202
- }
203
- }
204
- else if (isGlobal) {
205
- let tIn = 0, tOut = 0, tCache = 0;
206
- for (const e of events) {
207
- if (e.type === "TokenUsageReported") {
208
- try {
209
- const p = JSON.parse(e.payloadJson);
210
- tIn += (p.inputTokens ?? 0);
211
- tOut += (p.outputTokens ?? 0);
212
- tCache += (p.cacheReadTokens ?? 0);
213
- }
214
- catch { }
215
- }
216
- }
217
- tokensStr = ` \n Total Run Tokens: ${tIn} IN | ${tOut} OUT | ${tCache} CACHE`;
218
- }
219
- let bodyContent = null;
220
- if (tab === "input") {
221
- bodyContent = (<scrollbox style={{ width: "100%", height: "100%", paddingLeft: 1 }}>
222
- <text>{safePretty(inputData)}</text>
223
- </scrollbox>);
224
- }
225
- else if (tab === "output") {
226
- bodyContent = (<box style={{ width: "100%", height: "100%", flexDirection: "column" }}>
227
- <scrollbox style={{ flexGrow: 1, width: "100%", paddingLeft: 1 }}>
228
- <text>{safePretty(outputData)}</text>
229
- </scrollbox>
230
- {tokensStr && (<box style={{ width: "100%", height: 2, borderTop: true, borderColor: "gray" }}>
231
- <text style={{ color: "yellow" }}>{tokensStr}</text>
232
- </box>)}
233
- </box>);
234
- }
235
- else if (tab === "frames") {
236
- bodyContent = (<FramesPane adapter={adapter} runId={runId} focused={true} filterNodeId={nodeId ?? undefined} nodeAttempt={latestAttempt ?? undefined}/>);
237
- }
238
- else if (tab === "chat") {
239
- bodyContent = (<ChatPane adapter={adapter} runId={runId} focused={true} filterNodeId={nodeId ?? undefined}/>);
240
- }
241
- else if (tab === "logs") {
242
- bodyContent = (<LogsPane adapter={adapter} runId={runId} focused={true}/>);
243
- }
244
- else if (tab === "scores") {
245
- let scoresText = "No scorer results available.";
246
- if (scorerResults.length > 0) {
247
- const lines = scorerResults.map((r) => {
248
- const scoreVal = typeof r.score === "number" ? r.score.toFixed(2) : String(r.score);
249
- return ` ${r.scorerName ?? r.scorer_name ?? "unknown"}: ${scoreVal} ${r.reason ?? ""}`;
250
- });
251
- scoresText = `Scorer Results (${scorerResults.length}):\n\n${lines.join("\n")}`;
252
- }
253
- bodyContent = (<scrollbox style={{ width: "100%", height: "100%", paddingLeft: 1 }}>
254
- <text>{scoresText}</text>
255
- </scrollbox>);
256
- }
257
- /**
258
- * @param {string} num
259
- * @param {string} label
260
- * @param {string} expectedTab
261
- */
262
- const getTabLabel = (num, label, expectedTab) => {
263
- return tab === expectedTab ? `[(${num}) ${label}]` : ` (${num}) ${label} `;
264
- };
265
- const header = `Task Inspector: ${isGlobal ? "Entire Run" : nodeId}`;
266
- const tabs = `${getTabLabel("1", "Input", "input")} | ${getTabLabel("2", "Output", "output")} | ${getTabLabel("3", "Frames", "frames")} | ${getTabLabel("4", "Chat", "chat")} | ${getTabLabel("5", "Logs", "logs")} | ${getTabLabel("6", "Scores", "scores")}`;
267
- const escLabel = !isGlobal && latestAttempt ? "[R] Revert State | [Esc] Back" : "[Esc] Back";
268
- try {
269
- return (<box style={{ flexGrow: 1, width: "100%", height: "100%", flexDirection: "column" }}>
270
- <box style={{ width: "100%", height: 3, border: true, borderColor: "#3b82f6", flexDirection: "row", justifyContent: "space-between" }}>
271
- <text style={{ color: "white", paddingLeft: 1, fontWeight: "bold" }}>{header}</text>
272
- <text style={{ color: "#93c5fd", paddingRight: 1 }}>{tabs} {escLabel}</text>
273
- </box>
274
-
275
- <box style={{ flexGrow: 1, width: "100%", border: true, borderColor: "#60a5fa", flexDirection: "column" }}>
276
- {bodyContent}
277
- </box>
278
- </box>);
279
- }
280
- catch (err) {
281
- require("fs").writeFileSync("/tmp/tui-crash.log", err?.stack || err?.message || String(err));
282
- throw err;
283
- }
284
- }
@@ -1,7 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function NodeDetailView({ adapter, runId, nodeId, onBack, }: {
3
- adapter: SmithersDb;
4
- runId: string;
5
- nodeId: string | null;
6
- onBack: () => void;
7
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,51 +0,0 @@
1
- // @ts-nocheck
2
- import React, { useEffect, useState } from "react";
3
- import { useKeyboard } from "@opentui/react";
4
- import { ChatPane } from "./ChatPane.jsx";
5
- /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} SmithersDb */
6
-
7
- /**
8
- * @param {{ adapter: SmithersDb; runId: string; node: any; onClose: () => void; }} value
9
- */
10
- export function NodeInspector({ adapter, runId, node, onClose, }) {
11
- const [tab, setTab] = useState("snapshot");
12
- useKeyboard((key) => {
13
- if (key.name === "escape" || (key.name === "c" && key.ctrl) || key.name === "backspace") {
14
- onClose();
15
- }
16
- if (key.name === "1")
17
- setTab("snapshot");
18
- if (key.name === "2")
19
- setTab("chat");
20
- });
21
- return (<box style={{
22
- width: "90%",
23
- height: "90%",
24
- border: true,
25
- borderColor: "magenta",
26
- position: "absolute",
27
- top: "5%",
28
- left: "5%",
29
- flexDirection: "column",
30
- backgroundColor: "black",
31
- }} title={`[Esc to Close] Node Inspector: ${node.nodeId} | ${tab === "snapshot" ? "[(1) Snapshot] (2) Chat" : " (1) Snapshot [(2) Chat]"}`}>
32
- {tab === "snapshot" ? (<scrollbox style={{ width: "100%", height: "100%", flexDirection: "column", padding: 1 }}>
33
- <text style={{ color: "yellow" }}>
34
- <strong>Input Data:</strong>
35
- </text>
36
- <text>{node.inputData ? JSON.stringify(JSON.parse(node.inputData), null, 2) : "None"}</text>
37
- <box style={{ height: 1 }}/>
38
- <text style={{ color: "yellow" }}>
39
- <strong>Output Data:</strong>
40
- </text>
41
- <text>{node.outputData ? JSON.stringify(JSON.parse(node.outputData), null, 2) : "None"}</text>
42
- <box style={{ height: 1 }}/>
43
- <text style={{ color: "yellow" }}>
44
- <strong>Metadata:</strong>
45
- </text>
46
- <text>{`Iteration: ${node.iteration} | Attempts: ${node.attempts ?? 0} | State: ${node.state}`}</text>
47
- </scrollbox>) : (<box style={{ flexGrow: 1, width: "100%", height: "100%" }}>
48
- <ChatPane adapter={adapter} runId={runId} focused={true} filterNodeId={node.nodeId}/>
49
- </box>)}
50
- </box>);
51
- }
@@ -1,7 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function NodeInspector({ adapter, runId, node, onClose, }: {
3
- adapter: SmithersDb;
4
- runId: string;
5
- node: any;
6
- onClose: () => void;
7
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,190 +0,0 @@
1
- // @ts-nocheck
2
- import React, { useEffect, useState } from "react";
3
- import { useKeyboard } from "@opentui/react";
4
- /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} SmithersDb */
5
-
6
- /**
7
- * @param {{ adapter: SmithersDb; runId: string; onBack: () => void; onSelectNode: (nodeId: string | null) => void; }} value
8
- */
9
- export function RunDetailView({ adapter, runId, onBack, onSelectNode, }) {
10
- const [runData, setRunData] = useState(null);
11
- const [nodes, setNodes] = useState([]);
12
- const [attempts, setAttempts] = useState([]);
13
- const [events, setEvents] = useState([]);
14
- const [branchInfo, setBranchInfo] = useState(null);
15
- const [childBranches, setChildBranches] = useState([]);
16
- const [selectedIndex, setSelectedIndex] = useState(0); // 0 = Run itself, 1+ = nodes
17
- useEffect(() => {
18
- let mounted = true;
19
- let timeout;
20
- async function fetchDetails() {
21
- if (!mounted)
22
- return;
23
- try {
24
- const run = await adapter.getRun(runId);
25
- const fetchedNodes = await adapter.listNodes(runId);
26
- const fetchedAttempts = await adapter.listAttemptsForRun(runId);
27
- const fetchedEvents = await adapter.listEvents(runId, -1, 10000);
28
- // Time Travel: fetch branch info
29
- let fetchedBranchInfo = null;
30
- let fetchedChildBranches = [];
31
- try {
32
- const { getBranchInfo, listBranches } = await import("../../../time-travel/fork.js");
33
- fetchedBranchInfo = await getBranchInfo(adapter, runId) ?? null;
34
- fetchedChildBranches = await listBranches(adapter, runId) ?? [];
35
- }
36
- catch { }
37
- if (mounted) {
38
- setRunData(run);
39
- setNodes(fetchedNodes);
40
- setAttempts(fetchedAttempts);
41
- setEvents(fetchedEvents);
42
- setBranchInfo(fetchedBranchInfo);
43
- setChildBranches(fetchedChildBranches);
44
- setSelectedIndex((prev) => Math.min(prev, fetchedNodes.length));
45
- }
46
- }
47
- catch (err) { }
48
- if (mounted)
49
- timeout = setTimeout(fetchDetails, 1000);
50
- }
51
- fetchDetails();
52
- return () => {
53
- mounted = false;
54
- clearTimeout(timeout);
55
- };
56
- }, [adapter, runId]);
57
- useKeyboard((key) => {
58
- if (key.name === "escape" || (key.name === "c" && key.ctrl) || key.name === "backspace") {
59
- onBack();
60
- return;
61
- }
62
- if (key.name === "down" || key.name === "j") {
63
- setSelectedIndex((s) => Math.min(s + 1, nodes.length));
64
- }
65
- if (key.name === "up" || key.name === "k") {
66
- setSelectedIndex((s) => Math.max(0, s - 1));
67
- }
68
- if (key.name === "enter" || key.name === "return") {
69
- onSelectNode(selectedIndex === 0 ? null : nodes[selectedIndex - 1]?.nodeId);
70
- }
71
- if (key.name === "h" && selectedIndex > 0) {
72
- const targetNode = nodes[selectedIndex - 1]?.nodeId;
73
- if (targetNode && (runData?.status === "running" || runData?.status === "waiting-approval")) {
74
- Bun.spawn([
75
- "smithers-ctl",
76
- "terminal",
77
- "--cwd",
78
- process.cwd(),
79
- "--command",
80
- `bun run src/index.js hijack ${runId} --target ${targetNode}`,
81
- ], { stdout: "ignore", stderr: "ignore" }).unref();
82
- }
83
- }
84
- });
85
- if (!runData) {
86
- return <text style={{ margin: 1 }}>Loading run details for {runId}...</text>;
87
- }
88
- const isGlobal = selectedIndex === 0;
89
- const selectedNode = isGlobal ? null : nodes[selectedIndex - 1];
90
- let outputData = "No output text available yet.";
91
- if (isGlobal) {
92
- if (runData.errorJson) {
93
- outputData = `ERROR:\n${runData.errorJson}`;
94
- }
95
- else if (runData.status === "finished") {
96
- outputData = "Run completed successfully (no global errorJson stacktrace was captured).\n\nPress [Enter] to inspect individual task payloads.";
97
- }
98
- else if (runData.status === "failed") {
99
- outputData = "Workflow failed (no global errorJson stacktrace was captured).\n\nPress [Enter] to inspect and determine which individual task node crashed.";
100
- }
101
- else {
102
- outputData = "Workflow is still active or pending execution...";
103
- }
104
- }
105
- else {
106
- const nodeAttempts = attempts.filter((a) => a.nodeId === selectedNode?.nodeId).sort((a, b) => b.attempt - a.attempt);
107
- const latestAttempt = nodeAttempts[0];
108
- outputData = latestAttempt?.errorJson ? `ERROR:\n${latestAttempt.errorJson}` : latestAttempt?.responseText ?? "No output text available yet.";
109
- }
110
- let truncatedOutput = outputData;
111
- if (truncatedOutput.length > 500) {
112
- const head = truncatedOutput.substring(0, 200);
113
- const tail = truncatedOutput.substring(truncatedOutput.length - 200);
114
- truncatedOutput = `${head}\n\n... [ TRUNCATED ${truncatedOutput.length - 400} CHARACTERS ] ...\n\n${tail}`;
115
- }
116
- // Calculate tokens
117
- let tokensStr = "";
118
- if (!isGlobal && selectedNode) {
119
- const usageAttempts = attempts.filter((a) => a.nodeId === selectedNode.nodeId).sort((a, b) => b.attempt - a.attempt);
120
- const usageAttempt = usageAttempts[0];
121
- const usageEvent = events.find((e) => e.type === "TokenUsageReported" && e.nodeId === selectedNode.nodeId && e.attempt === usageAttempt?.attempt);
122
- if (usageEvent) {
123
- try {
124
- const payload = JSON.parse(usageEvent.payloadJson);
125
- tokensStr = `Tokens: ${payload.inputTokens} IN | ${payload.outputTokens} OUT | ${payload.cacheReadTokens ?? 0} CACHE`;
126
- }
127
- catch { }
128
- }
129
- }
130
- else if (isGlobal) {
131
- let tIn = 0, tOut = 0, tCache = 0;
132
- for (const e of events) {
133
- if (e.type === "TokenUsageReported") {
134
- try {
135
- const p = JSON.parse(e.payloadJson);
136
- tIn += (p.inputTokens ?? 0);
137
- tOut += (p.outputTokens ?? 0);
138
- tCache += (p.cacheReadTokens ?? 0);
139
- }
140
- catch { }
141
- }
142
- }
143
- tokensStr = `Total Run Tokens: ${tIn} IN | ${tOut} OUT | ${tCache} CACHE`;
144
- }
145
- return (<box style={{ flexGrow: 1, width: "100%", height: "100%", flexDirection: "row" }}>
146
- {/* Left Sidebar: Nodes Tree */}
147
- <box style={{
148
- width: 40,
149
- height: "100%",
150
- border: true,
151
- borderColor: "#34d399",
152
- flexDirection: "column",
153
- }} title={`Run Tasks [Esc to Return]`}>
154
- <scrollbox style={{ width: "100%", height: "100%", flexDirection: "column", paddingLeft: 1 }}>
155
- <text style={{ color: isGlobal ? "green" : "white" }}>
156
- {isGlobal ? "▶ " : " "}[ Entire Run ]
157
- </text>
158
- {nodes.map((node, i) => {
159
- const isSelected = selectedIndex === i + 1;
160
- return (<text key={`${node.runId}-${node.nodeId}-${node.iteration}`} style={{ color: isSelected ? "green" : "white" }}>
161
- {isSelected ? "▶ " : " "}{node.nodeId}: {node.state}
162
- </text>);
163
- })}
164
- </scrollbox>
165
- </box>
166
-
167
- {/* Right Area: Preview Pane */}
168
- <box style={{
169
- flexGrow: 1,
170
- height: "100%",
171
- border: true,
172
- borderColor: "#4bc5a3",
173
- flexDirection: "column",
174
- paddingLeft: 1
175
- }} title={`Preview: ${isGlobal ? "Entire Run" : selectedNode?.nodeId} [Hit Enter to Deep Inspect]`}>
176
- <text style={{ color: "yellow" }}>State: {isGlobal ? runData.status : selectedNode?.state}</text>
177
- {isGlobal && branchInfo && (<text style={{ color: "magenta" }}>
178
- Fork: from {branchInfo.parentRunId?.slice(0, 12)} frame {branchInfo.parentFrameNo}{branchInfo.branchLabel ? ` [${branchInfo.branchLabel}]` : ""}
179
- </text>)}
180
- {isGlobal && childBranches.length > 0 && (<text style={{ color: "cyan" }}>
181
- Branches: {childBranches.length} fork{childBranches.length !== 1 ? "s" : ""} ({childBranches.map((b) => b.branchLabel || b.runId?.slice(0, 8)).join(", ")})
182
- </text>)}
183
- {tokensStr && <text style={{ color: "cyan" }}>{tokensStr}</text>}
184
- <text style={{ color: "gray", marginTop: 1 }}>--- Terminal Output Snippet ---</text>
185
- <scrollbox style={{ flexGrow: 1, width: "100%", marginTop: 1 }}>
186
- <text>{truncatedOutput}</text>
187
- </scrollbox>
188
- </box>
189
- </box>);
190
- }
@@ -1,7 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function RunDetailView({ adapter, runId, onBack, onSelectNode, }: {
3
- adapter: SmithersDb;
4
- runId: string;
5
- onBack: () => void;
6
- onSelectNode: (nodeId: string | null) => void;
7
- }): import("react/jsx-runtime").JSX.Element;