@smithers-orchestrator/cli 0.20.0 → 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 (37) hide show
  1. package/dist/agent-detection.d.ts +20 -3
  2. package/package.json +17 -19
  3. package/src/AgentAvailability.ts +3 -0
  4. package/src/agent-detection.js +226 -14
  5. package/src/ask.js +4 -6
  6. package/src/index.js +89 -45
  7. package/src/workflow-pack.js +48 -10
  8. package/src/tui/app.jsx +0 -139
  9. package/src/tui/app.tsx +0 -5
  10. package/src/tui/components/AskModal.jsx +0 -109
  11. package/src/tui/components/AskModal.tsx +0 -3
  12. package/src/tui/components/AttentionPane.jsx +0 -112
  13. package/src/tui/components/AttentionPane.tsx +0 -6
  14. package/src/tui/components/ChatPane.jsx +0 -57
  15. package/src/tui/components/ChatPane.tsx +0 -7
  16. package/src/tui/components/CronList.jsx +0 -87
  17. package/src/tui/components/CronList.tsx +0 -5
  18. package/src/tui/components/DetailsPane.jsx +0 -96
  19. package/src/tui/components/DetailsPane.tsx +0 -7
  20. package/src/tui/components/FramesPane.jsx +0 -147
  21. package/src/tui/components/FramesPane.tsx +0 -8
  22. package/src/tui/components/LogsPane.jsx +0 -46
  23. package/src/tui/components/LogsPane.tsx +0 -6
  24. package/src/tui/components/MetricsPane.jsx +0 -108
  25. package/src/tui/components/MetricsPane.tsx +0 -5
  26. package/src/tui/components/NodeDetailView.jsx +0 -284
  27. package/src/tui/components/NodeDetailView.tsx +0 -7
  28. package/src/tui/components/NodeInspector.jsx +0 -51
  29. package/src/tui/components/NodeInspector.tsx +0 -7
  30. package/src/tui/components/RunDetailView.jsx +0 -190
  31. package/src/tui/components/RunDetailView.tsx +0 -7
  32. package/src/tui/components/RunsList.jsx +0 -184
  33. package/src/tui/components/RunsList.tsx +0 -7
  34. package/src/tui/components/SqliteBrowser.jsx +0 -131
  35. package/src/tui/components/SqliteBrowser.tsx +0 -5
  36. package/src/tui/components/WorkflowLauncher.jsx +0 -63
  37. package/src/tui/components/WorkflowLauncher.tsx +0 -3
@@ -1,112 +0,0 @@
1
- // @ts-nocheck
2
- import React, { useEffect, useState, useCallback } from "react";
3
- import { useKeyboard } from "@opentui/react";
4
- import { formatAge } from "../../format.js";
5
- /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} SmithersDb */
6
-
7
- /**
8
- * @param {{ adapter: SmithersDb; focused: boolean; onSelectRun?: (runId: string) => void; }} value
9
- */
10
- export function AttentionPane({ adapter, focused, onSelectRun, }) {
11
- const [items, setItems] = useState([]);
12
- const [selectedIndex, setSelectedIndex] = useState(0);
13
- useEffect(() => {
14
- let mounted = true;
15
- async function poll() {
16
- if (!mounted)
17
- return;
18
- try {
19
- const result = [];
20
- // Active alerts
21
- const alerts = await adapter.listAlerts(100, ["firing", "acknowledged"]);
22
- for (const alert of alerts) {
23
- result.push({
24
- kind: "alert",
25
- id: alert.alertId,
26
- severity: alert.severity,
27
- status: alert.status,
28
- runId: alert.runId ?? null,
29
- nodeId: alert.nodeId ?? null,
30
- message: alert.message,
31
- firedAtMs: alert.firedAtMs ?? null,
32
- });
33
- }
34
- // Pending approvals
35
- const runs = await adapter.listRuns(100);
36
- for (const run of runs) {
37
- const pending = await adapter.listPendingApprovals(run.runId);
38
- for (const ap of pending) {
39
- result.push({
40
- kind: "approval",
41
- id: `${ap.runId}:${ap.nodeId}:${ap.iteration ?? 0}`,
42
- severity: "info",
43
- status: "pending",
44
- runId: ap.runId,
45
- nodeId: ap.nodeId,
46
- message: ap.note ?? `Approval for ${ap.nodeId}`,
47
- firedAtMs: ap.requestedAtMs ?? null,
48
- });
49
- }
50
- }
51
- // Sort: critical first, then warning, then info
52
- const order = { critical: 0, warning: 1, info: 2 };
53
- result.sort((a, b) => (order[a.severity] ?? 3) - (order[b.severity] ?? 3));
54
- if (mounted)
55
- setItems(result);
56
- }
57
- catch { }
58
- if (mounted)
59
- setTimeout(poll, 2000);
60
- }
61
- poll();
62
- return () => { mounted = false; };
63
- }, [adapter]);
64
- const selected = items[selectedIndex];
65
- useKeyboard(focused, useCallback((key) => {
66
- if (key === "up" || key === "k") {
67
- setSelectedIndex((i) => Math.max(0, i - 1));
68
- }
69
- else if (key === "down" || key === "j") {
70
- setSelectedIndex((i) => Math.min(items.length - 1, i + 1));
71
- }
72
- else if (key === "a" && selected?.kind === "alert" && selected.status === "firing") {
73
- // Ack
74
- void adapter.acknowledgeAlert(selected.id, Date.now());
75
- }
76
- else if (key === "r" && selected?.kind === "alert") {
77
- // Resolve
78
- void adapter.resolveAlert(selected.id, Date.now());
79
- }
80
- else if (key === "s" && selected?.kind === "alert") {
81
- // Silence for 1h
82
- void adapter.silenceAlert(selected.id, Date.now() + 3_600_000);
83
- }
84
- else if (key === "enter" && selected?.runId && onSelectRun) {
85
- onSelectRun(selected.runId);
86
- }
87
- }, [items, selectedIndex, selected, adapter, onSelectRun]));
88
- const severityCounts = {
89
- critical: items.filter((i) => i.severity === "critical").length,
90
- warning: items.filter((i) => i.severity === "warning").length,
91
- info: items.filter((i) => i.severity === "info").length,
92
- };
93
- const header = [
94
- `Attention (${items.length})`,
95
- severityCounts.critical > 0 ? ` 🔴${severityCounts.critical}` : "",
96
- severityCounts.warning > 0 ? ` 🟡${severityCounts.warning}` : "",
97
- severityCounts.info > 0 ? ` 🔵${severityCounts.info}` : "",
98
- ].join("");
99
- return (<box flexDirection="column">
100
- <text bold>{header}</text>
101
- <text dimColor> [a]ck [r]esolve [s]ilence [Enter] open run</text>
102
- {items.length === 0 ? (<text dimColor> All clear — no attention items.</text>) : (items.map((item, i) => {
103
- const isSelected = i === selectedIndex && focused;
104
- const sev = item.severity === "critical" ? "🔴" : item.severity === "warning" ? "🟡" : "🔵";
105
- const age = item.firedAtMs ? formatAge(item.firedAtMs) : "";
106
- const prefix = isSelected ? "▸ " : " ";
107
- return (<text key={item.id} inverse={isSelected}>
108
- {prefix}{sev} [{item.kind}] {item.message} ({item.status}) {age}
109
- </text>);
110
- }))}
111
- </box>);
112
- }
@@ -1,6 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function AttentionPane({ adapter, focused, onSelectRun, }: {
3
- adapter: SmithersDb;
4
- focused: boolean;
5
- onSelectRun?: (runId: string) => void;
6
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,57 +0,0 @@
1
- // @ts-nocheck
2
- import React, { useEffect, useState } from "react";
3
- import { formatChatBlock, parseChatAttemptMeta, selectChatAttempts } from "../../chat.js";
4
- /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} SmithersDb */
5
-
6
- /**
7
- * @param {{ adapter: SmithersDb; runId: string; focused: boolean; filterNodeId?: string; }} value
8
- */
9
- export function ChatPane({ adapter, runId, focused, filterNodeId, }) {
10
- const [chatLines, setChatLines] = useState([]);
11
- useEffect(() => {
12
- let mounted = true;
13
- async function fetchChat() {
14
- if (!mounted)
15
- return;
16
- try {
17
- const attempts = await adapter.listAttemptsForRun(runId);
18
- let lines = [];
19
- for (const attempt of attempts) {
20
- if (filterNodeId && attempt.nodeId !== filterNodeId)
21
- continue;
22
- const meta = parseChatAttemptMeta(attempt.metaJson ?? "");
23
- if (!meta.prompt && !attempt.responseText)
24
- continue; // Skip empty attempts
25
- if (lines.length > 0)
26
- lines.push("");
27
- lines.push(`=== ${attempt.nodeId} (Attempt ${attempt.attempt}, Iteration ${attempt.iteration}) ===`);
28
- if (meta.prompt) {
29
- lines.push(`[USER]`);
30
- lines.push(...String(meta.prompt).trim().split("\n").map(l => ` ${l}`));
31
- lines.push("");
32
- }
33
- if (attempt.responseText) {
34
- lines.push(`[ASSISTANT]`);
35
- lines.push(...String(attempt.responseText).trim().split("\n").map(l => ` ${l}`));
36
- }
37
- }
38
- if (mounted) {
39
- setChatLines(lines);
40
- }
41
- }
42
- catch (err) { }
43
- if (mounted)
44
- setTimeout(fetchChat, 1000);
45
- }
46
- fetchChat();
47
- return () => {
48
- mounted = false;
49
- };
50
- }, [adapter, runId]);
51
- return (<scrollbox focused={focused} style={{ width: "100%", height: "100%", paddingLeft: 1, paddingRight: 1 }}>
52
- <box flexDirection="column">
53
- {chatLines.map((line, index) => (<text key={index}>{line}</text>))}
54
- {chatLines.length === 0 && <text>No chat history available.</text>}
55
- </box>
56
- </scrollbox>);
57
- }
@@ -1,7 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function ChatPane({ adapter, runId, focused, filterNodeId, }: {
3
- adapter: SmithersDb;
4
- runId: string;
5
- focused: boolean;
6
- filterNodeId?: string;
7
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,87 +0,0 @@
1
- // @ts-nocheck
2
- import React, { useEffect, useState } from "react";
3
- import { useKeyboard } from "@opentui/react";
4
- import { formatAge } from "../../format.js";
5
- /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} SmithersDb */
6
-
7
- /**
8
- * @param {{ adapter: SmithersDb; onBack: () => void; }} value
9
- */
10
- export function CronList({ adapter, onBack, }) {
11
- const [crons, setCrons] = useState([]);
12
- const [selectedIndex, setSelectedIndex] = useState(0);
13
- useEffect(() => {
14
- let mounted = true;
15
- async function poll() {
16
- try {
17
- const jobs = await adapter.listCrons(false);
18
- if (mounted)
19
- setCrons(jobs);
20
- }
21
- catch { }
22
- if (mounted)
23
- setTimeout(poll, 2000);
24
- }
25
- poll();
26
- return () => { mounted = false; };
27
- }, [adapter]);
28
- useKeyboard(async (key) => {
29
- if (key.name === "escape") {
30
- onBack();
31
- return;
32
- }
33
- if (crons.length > 0) {
34
- if (key.name === "down" || key.name === "j") {
35
- setSelectedIndex(Math.min(crons.length - 1, selectedIndex + 1));
36
- }
37
- else if (key.name === "up" || key.name === "k") {
38
- setSelectedIndex(Math.max(0, selectedIndex - 1));
39
- }
40
- else if (key.name === "backspace" || key.name === "delete") {
41
- const id = crons[selectedIndex].cronId;
42
- await adapter.deleteCron(id);
43
- const next = await adapter.listCrons(false);
44
- setCrons(next);
45
- setSelectedIndex(Math.max(0, Math.min(selectedIndex, next.length - 1)));
46
- }
47
- }
48
- });
49
- const selectedJob = crons[selectedIndex];
50
- return (<box style={{ flexGrow: 1, width: "100%", height: "100%", flexDirection: "row" }}>
51
- {/* Left List */}
52
- <box style={{ width: 45, height: "100%", borderRight: true, borderColor: "#34d399", flexDirection: "column" }}>
53
- <text style={{ color: "gray", marginBottom: 1 }}> Active Cron Triggers: {crons.length} </text>
54
- {crons.map((c, i) => (<text key={c.cronId} style={{ color: i === selectedIndex ? "#a7f3d0" : "white" }}>
55
- {i === selectedIndex ? "▶ " : " "}{c.workflowPath.slice(0, 30)}
56
- </text>))}
57
- </box>
58
-
59
- {/* Right Details */}
60
- <box style={{ flexGrow: 1, height: "100%", flexDirection: "column", paddingLeft: 1 }}>
61
- {selectedJob ? (<box style={{ flexDirection: "column" }}>
62
- <text style={{ color: "yellow" }}> Workflow: {selectedJob.workflowPath} </text>
63
- <text style={{ color: "#93c5fd" }}> Setup: {selectedJob.pattern} </text>
64
- <text style={{ color: "white", marginTop: 1 }}>
65
- Status: {selectedJob.enabled ? "ACTIVE" : "PAUSED"}
66
- </text>
67
- <text style={{ color: "white" }}>
68
- Registered: {formatAge(selectedJob.createdAtMs)}
69
- </text>
70
- <text style={{ color: "white" }}>
71
- Last Pired: {selectedJob.lastRunAtMs ? formatAge(selectedJob.lastRunAtMs) : "Never"}
72
- </text>
73
- <text style={{ color: "cyan" }}>
74
- Next Fire: {selectedJob.nextRunAtMs ? formatAge(selectedJob.nextRunAtMs) : "Pending"}
75
- </text>
76
-
77
- <box style={{ marginTop: 2, flexDirection: "column", borderTop: true, borderColor: "gray", paddingTop: 1 }}>
78
- <text style={{ color: "red" }}>[Backspace] Kill Trigger</text>
79
- </box>
80
-
81
- {selectedJob.errorJson && (<text style={{ color: "red", marginTop: 1 }}>
82
- Last Error: {selectedJob.errorJson}
83
- </text>)}
84
- </box>) : (<text style={{ color: "gray" }}>No schedules found. Run `smithers cron add`.</text>)}
85
- </box>
86
- </box>);
87
- }
@@ -1,5 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function CronList({ adapter, onBack, }: {
3
- adapter: SmithersDb;
4
- onBack: () => void;
5
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,96 +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; focused: boolean; onInspectNode?: (node: any) => void; }} value
8
- */
9
- export function DetailsPane({ adapter, runId, focused, onInspectNode, }) {
10
- const [nodes, setNodes] = useState([]);
11
- const [runData, setRunData] = useState(null);
12
- const [selectedIndex, setSelectedIndex] = useState(0);
13
- useEffect(() => {
14
- let mounted = true;
15
- let timeout;
16
- async function fetchDetails() {
17
- if (!mounted)
18
- return;
19
- try {
20
- const run = await adapter.getRun(runId);
21
- const fetchedNodes = await adapter.listNodes(runId);
22
- if (mounted) {
23
- setRunData(run);
24
- setNodes(fetchedNodes);
25
- setSelectedIndex((prev) => Math.min(prev, Math.max(0, fetchedNodes.length - 1)));
26
- }
27
- }
28
- catch (err) { }
29
- if (mounted)
30
- timeout = setTimeout(fetchDetails, 1000);
31
- }
32
- fetchDetails();
33
- return () => {
34
- mounted = false;
35
- clearTimeout(timeout);
36
- };
37
- }, [adapter, runId]);
38
- useKeyboard((key) => {
39
- if (!focused)
40
- return;
41
- if (key.name === "down" || key.name === "j") {
42
- setSelectedIndex((s) => Math.min(s + 1, Math.max(0, nodes.length - 1)));
43
- }
44
- if (key.name === "up" || key.name === "k") {
45
- setSelectedIndex((s) => Math.max(0, s - 1));
46
- }
47
- if (key.name === "enter" || key.name === "return") {
48
- if (nodes[selectedIndex] && onInspectNode) {
49
- onInspectNode(nodes[selectedIndex]);
50
- }
51
- }
52
- });
53
- if (!runData) {
54
- return <text style={{ margin: 1 }}>Loading details...</text>;
55
- }
56
- return (<scrollbox focused={focused} style={{ width: "100%", height: "100%", padding: 1 }}>
57
- <box flexDirection="column">
58
- <text>
59
- <strong>Status:</strong>{" "}
60
- <span fg={runData.status === "finished"
61
- ? "green"
62
- : runData.status === "failed"
63
- ? "red"
64
- : runData.status === "running"
65
- ? "#34d399"
66
- : "yellow"}>
67
- {runData.status}
68
- </span>
69
- </text>
70
- <text>
71
- <strong>Input Payload:</strong>
72
- </text>
73
- <text>{runData.inputData ? runData.inputData.substring(0, 100) + "..." : "None"}</text>
74
-
75
- {runData.outputData && (<>
76
- <box style={{ height: 1 }}/>
77
- <text>
78
- <strong>Final Output:</strong>
79
- </text>
80
- <text>{runData.outputData.substring(0, 100) + "..."}</text>
81
- </>)}
82
-
83
- <box style={{ height: 1 }}/>
84
- <text style={{ color: focused ? "yellow" : "white" }}>
85
- <strong>Nodes [Press Enter on Node to Inspect]:</strong>
86
- </text>
87
-
88
- {nodes.map((node, i) => {
89
- const isSelected = focused && selectedIndex === i;
90
- return (<text key={`${node.runId}-${node.nodeId}-${node.iteration}`} style={{ color: isSelected ? "green" : "white" }}>
91
- {isSelected ? "▶ " : " "}{node.nodeId}: {node.state} (Att: {node.attempts ?? 0}, Iter: {node.iteration})
92
- </text>);
93
- })}
94
- </box>
95
- </scrollbox>);
96
- }
@@ -1,7 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function DetailsPane({ adapter, runId, focused, onInspectNode, }: {
3
- adapter: SmithersDb;
4
- runId: string;
5
- focused: boolean;
6
- onInspectNode?: (node: any) => void;
7
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,147 +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; focused: boolean; filterNodeId?: string; nodeAttempt?: any; }} value
8
- */
9
- export function FramesPane({ adapter, runId, focused, filterNodeId, nodeAttempt, }) {
10
- const [frames, setFrames] = useState([]);
11
- const [selectedIndex, setSelectedIndex] = useState(0);
12
- useEffect(() => {
13
- let mounted = true;
14
- async function fetchFrames() {
15
- if (!mounted)
16
- return;
17
- try {
18
- // limit(500) to ensure we get a chunk safely, reverse to ASCENDING chronological order
19
- const data = await adapter.listFrames(runId, 500);
20
- if (mounted && data.length > 0) {
21
- const ascData = data.slice().reverse();
22
- setFrames(ascData);
23
- if (frames.length === 0) {
24
- setSelectedIndex(ascData.length - 1); // default to newest frame (bottom)
25
- }
26
- }
27
- }
28
- catch (err) { }
29
- if (mounted)
30
- setTimeout(fetchFrames, 2000); // Polling for live updates
31
- }
32
- fetchFrames();
33
- return () => {
34
- mounted = false;
35
- };
36
- }, [adapter, runId]);
37
- let displayFrames = frames;
38
- if (filterNodeId && nodeAttempt && frames.length > 0) {
39
- const sMs = nodeAttempt.startedAtMs ?? 0;
40
- const fMs = nodeAttempt.finishedAtMs;
41
- const beforeF = frames.slice().reverse().find(f => f.createdAtMs <= sMs) || frames[0];
42
- const afterF = fMs ? (frames.find(f => f.createdAtMs >= fMs) || frames[frames.length - 1]) : frames[frames.length - 1];
43
- displayFrames = frames.map(f => {
44
- if (f.frameNo === beforeF.frameNo && f.frameNo === afterF.frameNo) {
45
- return { ...f, uiLabel: "Frame (Active)" };
46
- }
47
- if (f.frameNo === beforeF.frameNo)
48
- return { ...f, uiLabel: "Frame (Before)" };
49
- if (f.frameNo === afterF.frameNo)
50
- return { ...f, uiLabel: "Frame (After)" };
51
- return f;
52
- });
53
- }
54
- useKeyboard((key) => {
55
- if (!focused || displayFrames.length === 0)
56
- return;
57
- if (key.name === "up" || key.name === "k") {
58
- setSelectedIndex((s) => Math.max(0, s - 1));
59
- }
60
- if (key.name === "down" || key.name === "j") {
61
- setSelectedIndex((s) => Math.min(s + 1, displayFrames.length - 1));
62
- }
63
- });
64
- if (displayFrames.length === 0) {
65
- return <text style={{ paddingLeft: 1 }}>No frame history available...</text>;
66
- }
67
- // Bound check in case selectedIndex drifted
68
- const validIndex = Math.max(0, Math.min(selectedIndex, displayFrames.length - 1));
69
- const selectedFrame = displayFrames[validIndex];
70
- // Recursive formatter to convert `xmlJson` into a JSX code block
71
- /**
72
- * @param {any} node
73
- * @param {number} [indent]
74
- * @returns {string}
75
- */
76
- function formatJsxNode(node, indent = 0) {
77
- if (!node || typeof node !== "object")
78
- return "";
79
- const space = " ".repeat(indent);
80
- if (node.kind === "text") {
81
- const escaped = String(node.text || "").replace(/\n/g, `\n${space}`);
82
- return `${space}${escaped}`;
83
- }
84
- if (node.kind === "element" && typeof node.tag === "string") {
85
- let propsStr = "";
86
- if (node.props && typeof node.props === "object") {
87
- for (const [k, v] of Object.entries(node.props)) {
88
- if (typeof v === "string")
89
- propsStr += ` ${k}="${v.replace(/"/g, '&quot;')}"`;
90
- else
91
- propsStr += ` ${k}={${JSON.stringify(v)}}`;
92
- }
93
- }
94
- const tag = node.tag;
95
- const isTargetNode = filterNodeId && node.props?.id === filterNodeId;
96
- const colorPrefix = isTargetNode ? "👉 " : "";
97
- if (!Array.isArray(node.children) || node.children.length === 0) {
98
- return `${space}${colorPrefix}<${tag}${propsStr} />`;
99
- }
100
- let res = `${space}${colorPrefix}<${tag}${propsStr}>\n`;
101
- for (const child of node.children) {
102
- res += formatJsxNode(child, indent + 1) + "\n";
103
- }
104
- res += `${space}</${tag}>`;
105
- return res;
106
- }
107
- // Fallback if neither text nor element
108
- return `${space}${JSON.stringify(node)}`;
109
- }
110
- let xmlString = "Empty Frame";
111
- try {
112
- if (selectedFrame?.xmlJson) {
113
- const parsed = typeof selectedFrame.xmlJson === "string" ? JSON.parse(selectedFrame.xmlJson) : selectedFrame.xmlJson;
114
- xmlString = formatJsxNode(parsed);
115
- }
116
- }
117
- catch (err) {
118
- xmlString = `[Format Error: ${err?.message}]\n\n${selectedFrame?.xmlJson ?? "Parse error"}`;
119
- }
120
- return (<box style={{ flexGrow: 1, width: "100%", height: "100%", flexDirection: "row" }}>
121
- {/* Left Sidebar: Frame List */}
122
- <box style={{
123
- width: 30,
124
- height: "100%",
125
- borderRight: true,
126
- borderColor: "#34d399",
127
- flexDirection: "column",
128
- }} title={`Timeline [Up/Down]`}>
129
- <scrollbox style={{ width: "100%", height: "100%", flexDirection: "column", paddingLeft: 1 }}>
130
- {displayFrames.map((frame, i) => {
131
- const isSelected = validIndex === i;
132
- const label = frame.uiLabel ? `${frame.uiLabel}` : `Frame ${frame.frameNo}`;
133
- return (<text key={frame.frameNo + label} style={{ color: isSelected ? "green" : "white" }}>
134
- {isSelected ? "▶ " : " "}{label}
135
- </text>);
136
- })}
137
- </scrollbox>
138
- </box>
139
-
140
- {/* Right Content: XML/JSX Dump */}
141
- <box style={{ flexGrow: 1, height: "100%", flexDirection: "column" }}>
142
- <scrollbox style={{ width: "100%", height: "100%", paddingLeft: 1 }}>
143
- <text style={{ color: "#d8b4e2" }}>{xmlString}</text>
144
- </scrollbox>
145
- </box>
146
- </box>);
147
- }
@@ -1,8 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function FramesPane({ adapter, runId, focused, filterNodeId, nodeAttempt, }: {
3
- adapter: SmithersDb;
4
- runId: string;
5
- focused: boolean;
6
- filterNodeId?: string;
7
- nodeAttempt?: any;
8
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,46 +0,0 @@
1
- // @ts-nocheck
2
- import React, { useEffect, useState } from "react";
3
- import { formatEventLine } from "../../format.js";
4
- /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} SmithersDb */
5
-
6
- /**
7
- * @param {{ adapter: SmithersDb; runId: string; focused: boolean; }} value
8
- */
9
- export function LogsPane({ adapter, runId, focused, }) {
10
- const [logs, setLogs] = useState([]);
11
- useEffect(() => {
12
- let mounted = true;
13
- let lastSeq = -1;
14
- async function fetchLogs() {
15
- if (!mounted)
16
- return;
17
- try {
18
- const events = await adapter.listEvents(runId, lastSeq, 200);
19
- if (mounted && events.length > 0) {
20
- const run = await adapter.getRun(runId);
21
- const baseMs = run?.startedAtMs ?? run?.createdAtMs ?? Date.now();
22
- const newLines = events.map((e) => formatEventLine(e, baseMs));
23
- lastSeq = events[events.length - 1].seq;
24
- setLogs((prev) => {
25
- const updated = [...prev, ...newLines];
26
- // keep the last 200 lines to avoid scrollbox lag
27
- return updated.slice(-200);
28
- });
29
- }
30
- }
31
- catch (err) { }
32
- if (mounted)
33
- setTimeout(fetchLogs, 500);
34
- }
35
- fetchLogs();
36
- return () => {
37
- mounted = false;
38
- };
39
- }, [adapter, runId]);
40
- return (<scrollbox focused={focused} style={{ width: "100%", height: "100%", paddingLeft: 1, paddingRight: 1 }}>
41
- <box flexDirection="column">
42
- {logs.map((log, index) => (<text key={index}>{log}</text>))}
43
- {logs.length === 0 && <text>Loading events...</text>}
44
- </box>
45
- </scrollbox>);
46
- }
@@ -1,6 +0,0 @@
1
- import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
2
- export declare function LogsPane({ adapter, runId, focused, }: {
3
- adapter: SmithersDb;
4
- runId: string;
5
- focused: boolean;
6
- }): import("react/jsx-runtime").JSX.Element;