opencode-top 3.1.2 → 3.2.1
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/bin/octop.js +2 -9
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +22432 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +22572 -0
- package/dist/core/agents.d.ts +11 -0
- package/dist/core/agents.d.ts.map +1 -0
- package/dist/core/agents.js +58 -0
- package/dist/core/agents.js.map +1 -0
- package/dist/core/session.d.ts +19 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +261 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/types.d.ts +140 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +29 -0
- package/dist/core/types.js.map +1 -0
- package/dist/data/pricing.d.ts +4 -0
- package/dist/data/pricing.d.ts.map +1 -0
- package/dist/data/pricing.js +76 -0
- package/dist/data/pricing.js.map +1 -0
- package/dist/data/sqlite.d.ts +5 -0
- package/dist/data/sqlite.d.ts.map +1 -0
- package/dist/data/sqlite.js +222 -0
- package/dist/data/sqlite.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +1 -0
- package/dist/index.js.map +1 -0
- package/dist/ui/App.d.ts +6 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +101 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/components/AgentChainGraph.d.ts +9 -0
- package/dist/ui/components/AgentChainGraph.d.ts.map +1 -0
- package/dist/ui/components/AgentChainGraph.js +41 -0
- package/dist/ui/components/AgentChainGraph.js.map +1 -0
- package/dist/ui/components/AgentTree.d.ts +13 -0
- package/dist/ui/components/AgentTree.d.ts.map +1 -0
- package/dist/ui/components/AgentTree.js +50 -0
- package/dist/ui/components/AgentTree.js.map +1 -0
- package/dist/ui/components/DetailsPanel.d.ts +9 -0
- package/dist/ui/components/DetailsPanel.d.ts.map +1 -0
- package/dist/ui/components/DetailsPanel.js +82 -0
- package/dist/ui/components/DetailsPanel.js.map +1 -0
- package/dist/ui/components/MessagesPanel.d.ts +12 -0
- package/dist/ui/components/MessagesPanel.d.ts.map +1 -0
- package/dist/ui/components/MessagesPanel.js +107 -0
- package/dist/ui/components/MessagesPanel.js.map +1 -0
- package/dist/ui/components/SparkLine.d.ts +10 -0
- package/dist/ui/components/SparkLine.d.ts.map +1 -0
- package/dist/ui/components/SparkLine.js +12 -0
- package/dist/ui/components/SparkLine.js.map +1 -0
- package/dist/ui/components/StatusBar.d.ts +9 -0
- package/dist/ui/components/StatusBar.d.ts.map +1 -0
- package/dist/ui/components/StatusBar.js +9 -0
- package/dist/ui/components/StatusBar.js.map +1 -0
- package/dist/ui/components/TabBar.d.ts +10 -0
- package/dist/ui/components/TabBar.d.ts.map +1 -0
- package/dist/ui/components/TabBar.js +17 -0
- package/dist/ui/components/TabBar.js.map +1 -0
- package/dist/ui/screens/OverviewScreen.d.ts +12 -0
- package/dist/ui/screens/OverviewScreen.d.ts.map +1 -0
- package/dist/ui/screens/OverviewScreen.js +94 -0
- package/dist/ui/screens/OverviewScreen.js.map +1 -0
- package/dist/ui/screens/SessionsScreen.d.ts +12 -0
- package/dist/ui/screens/SessionsScreen.d.ts.map +1 -0
- package/dist/ui/screens/SessionsScreen.js +98 -0
- package/dist/ui/screens/SessionsScreen.js.map +1 -0
- package/dist/ui/screens/TimelineScreen.d.ts +11 -0
- package/dist/ui/screens/TimelineScreen.d.ts.map +1 -0
- package/dist/ui/screens/TimelineScreen.js +128 -0
- package/dist/ui/screens/TimelineScreen.js.map +1 -0
- package/dist/ui/screens/ToolsScreen.d.ts +12 -0
- package/dist/ui/screens/ToolsScreen.d.ts.map +1 -0
- package/dist/ui/screens/ToolsScreen.js +113 -0
- package/dist/ui/screens/ToolsScreen.js.map +1 -0
- package/dist/ui/theme.d.ts +21 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +21 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +3 -1
- package/patches/ink+5.2.1.patch +13 -0
- package/bin/octop.mjs +0 -13
- package/src/cli.ts +0 -60
- package/src/core/agents.ts +0 -78
- package/src/core/session.ts +0 -315
- package/src/core/types.ts +0 -156
- package/src/data/pricing.ts +0 -82
- package/src/data/sqlite.ts +0 -347
- package/src/ui/App.tsx +0 -154
- package/src/ui/components/AgentChainGraph.tsx +0 -95
- package/src/ui/components/AgentTree.tsx +0 -101
- package/src/ui/components/DetailsPanel.tsx +0 -211
- package/src/ui/components/MessagesPanel.tsx +0 -323
- package/src/ui/components/SparkLine.tsx +0 -18
- package/src/ui/components/StatusBar.tsx +0 -24
- package/src/ui/components/TabBar.tsx +0 -42
- package/src/ui/screens/OverviewScreen.tsx +0 -327
- package/src/ui/screens/SessionsScreen.tsx +0 -168
- package/src/ui/screens/TimelineScreen.tsx +0 -222
- package/src/ui/screens/ToolsScreen.tsx +0 -260
- package/src/ui/theme.ts +0 -21
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import React, { useMemo, memo } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { colors } from "../theme";
|
|
4
|
-
import { StatusBar } from "../components/StatusBar";
|
|
5
|
-
import { SparkLine } from "../components/SparkLine";
|
|
6
|
-
import type { Workflow } from "../../core/types";
|
|
7
|
-
import { computeOverviewStats, buildSparkSeries } from "../../core/session";
|
|
8
|
-
import { getAllPricing } from "../../data/pricing";
|
|
9
|
-
|
|
10
|
-
interface OverviewScreenProps {
|
|
11
|
-
workflows: Workflow[];
|
|
12
|
-
isActive: boolean;
|
|
13
|
-
contentHeight: number;
|
|
14
|
-
terminalWidth: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function formatTokens(n: number): string {
|
|
18
|
-
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
19
|
-
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
20
|
-
return n.toString();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function StatRow({ label, value, color = colors.text }: { label: string; value: string; color?: string }) {
|
|
24
|
-
return (
|
|
25
|
-
<Box flexDirection="row">
|
|
26
|
-
<Box width={18}>
|
|
27
|
-
<Text color={colors.textDim}>{label}</Text>
|
|
28
|
-
</Box>
|
|
29
|
-
<Text color={color}>{value}</Text>
|
|
30
|
-
</Box>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function SectionHeader({ title }: { title: string }) {
|
|
35
|
-
return (
|
|
36
|
-
<Box marginTop={1}>
|
|
37
|
-
<Text color={colors.purple} bold>{title}</Text>
|
|
38
|
-
</Box>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Horizontal bar: filled █ proportional to value/max, with label and count */
|
|
43
|
-
function HBar({
|
|
44
|
-
label,
|
|
45
|
-
value,
|
|
46
|
-
max,
|
|
47
|
-
total,
|
|
48
|
-
width = 16,
|
|
49
|
-
barColor = colors.info,
|
|
50
|
-
labelWidth = 10,
|
|
51
|
-
showPct = false,
|
|
52
|
-
}: {
|
|
53
|
-
label: string;
|
|
54
|
-
value: number;
|
|
55
|
-
max: number;
|
|
56
|
-
total?: number;
|
|
57
|
-
width?: number;
|
|
58
|
-
barColor?: string;
|
|
59
|
-
labelWidth?: number;
|
|
60
|
-
showPct?: boolean;
|
|
61
|
-
}) {
|
|
62
|
-
const pct = max > 0 ? value / max : 0;
|
|
63
|
-
const filled = Math.max(0, Math.round(pct * width));
|
|
64
|
-
const empty = width - filled;
|
|
65
|
-
const pctStr = showPct && total && total > 0 ? ` ${Math.round((value / total) * 100)}%` : "";
|
|
66
|
-
return (
|
|
67
|
-
<Box flexDirection="row">
|
|
68
|
-
<Box width={labelWidth}>
|
|
69
|
-
<Text color={colors.text}>{truncate(label, labelWidth - 1)}</Text>
|
|
70
|
-
</Box>
|
|
71
|
-
<Text color={barColor}>{"█".repeat(filled)}</Text>
|
|
72
|
-
<Text color={colors.border}>{"░".repeat(empty)}</Text>
|
|
73
|
-
<Text color={colors.textDim}> {value}{pctStr}</Text>
|
|
74
|
-
</Box>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Error rate bar: shows ok + error segments */
|
|
79
|
-
function ErrorBar({
|
|
80
|
-
label,
|
|
81
|
-
calls,
|
|
82
|
-
errors,
|
|
83
|
-
width = 16,
|
|
84
|
-
labelWidth = 8,
|
|
85
|
-
}: {
|
|
86
|
-
label: string;
|
|
87
|
-
calls: number;
|
|
88
|
-
errors: number;
|
|
89
|
-
width: number;
|
|
90
|
-
labelWidth: number;
|
|
91
|
-
}) {
|
|
92
|
-
const okCount = calls - errors;
|
|
93
|
-
const okFilled = calls > 0 ? Math.round((okCount / calls) * width) : width;
|
|
94
|
-
const errFilled = width - okFilled;
|
|
95
|
-
const errPct = calls > 0 ? Math.round((errors / calls) * 100) : 0;
|
|
96
|
-
return (
|
|
97
|
-
<Box flexDirection="row">
|
|
98
|
-
<Box width={labelWidth}>
|
|
99
|
-
<Text color={colors.text}>{truncate(label, labelWidth - 1)}</Text>
|
|
100
|
-
</Box>
|
|
101
|
-
<Text color={colors.success}>{"█".repeat(okFilled)}</Text>
|
|
102
|
-
<Text color={errors > 0 ? colors.error : colors.border}>{"█".repeat(errFilled)}</Text>
|
|
103
|
-
<Text color={colors.textDim}> {calls} calls</Text>
|
|
104
|
-
{errors > 0 && <Text color={colors.error}> {errors} err ({errPct}%)</Text>}
|
|
105
|
-
</Box>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function OverviewScreenInner({ workflows, isActive, contentHeight, terminalWidth }: OverviewScreenProps) {
|
|
110
|
-
const pricing = useMemo(() => getAllPricing(), []);
|
|
111
|
-
const stats = useMemo(() => computeOverviewStats(workflows, pricing), [workflows, pricing]);
|
|
112
|
-
|
|
113
|
-
const topModels = useMemo(() =>
|
|
114
|
-
Array.from(stats.modelBreakdown.entries())
|
|
115
|
-
.sort((a, b) => b[1].calls - a[1].calls)
|
|
116
|
-
.slice(0, 4),
|
|
117
|
-
[stats]
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const topProjects = useMemo(() =>
|
|
121
|
-
Array.from(stats.projectBreakdown.entries())
|
|
122
|
-
.sort((a, b) => b[1].sessions - a[1].sessions)
|
|
123
|
-
.slice(0, 4),
|
|
124
|
-
[stats]
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
const agentToolErrorList = useMemo(() =>
|
|
128
|
-
Array.from(stats.agentToolErrors.entries())
|
|
129
|
-
.sort((a, b) => b[1].calls - a[1].calls),
|
|
130
|
-
[stats]
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
const topTools = useMemo(() =>
|
|
134
|
-
Array.from(stats.toolCallCounts.entries())
|
|
135
|
-
.sort((a, b) => b[1].calls - a[1].calls)
|
|
136
|
-
.slice(0, 8),
|
|
137
|
-
[stats]
|
|
138
|
-
);
|
|
139
|
-
const maxToolCalls = topTools[0]?.[1].calls ?? 1;
|
|
140
|
-
|
|
141
|
-
// Weekly sparklines
|
|
142
|
-
const weeklyTokenValues = stats.weeklyTokens.map((d) => d.tokens);
|
|
143
|
-
const weeklySessionValues = stats.weeklySessions.map((d) => d.sessions);
|
|
144
|
-
const maxWeeklyTokens = Math.max(...weeklyTokenValues, 1);
|
|
145
|
-
const maxWeeklySessions = Math.max(...weeklySessionValues, 1);
|
|
146
|
-
|
|
147
|
-
// Hourly heatmap — group into 4-hour buckets for compactness: 0-3,4-7,8-11,12-15,16-19,20-23
|
|
148
|
-
const hourlyBuckets = Array.from({ length: 6 }, (_, i) =>
|
|
149
|
-
stats.hourlyActivity.slice(i * 4, i * 4 + 4).reduce((a, b) => a + b, 0)
|
|
150
|
-
);
|
|
151
|
-
const hourSpark = buildSparkSeries(stats.hourlyActivity);
|
|
152
|
-
|
|
153
|
-
// Column widths based on terminal
|
|
154
|
-
const leftW = Math.max(32, Math.floor(terminalWidth * 0.38));
|
|
155
|
-
const midW = Math.max(26, Math.floor(terminalWidth * 0.30));
|
|
156
|
-
// right gets remaining
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<Box flexDirection="column" width={terminalWidth} height={contentHeight}>
|
|
160
|
-
<Box paddingX={1} flexDirection="row">
|
|
161
|
-
<Text color={colors.accent} bold>Overview</Text>
|
|
162
|
-
<Box flexGrow={1} />
|
|
163
|
-
<Text color={colors.textDim}>{workflows.length} workflows {stats.totalTokens.total > 0 ? formatTokens(stats.totalTokens.total) + " tokens" : ""}</Text>
|
|
164
|
-
</Box>
|
|
165
|
-
|
|
166
|
-
<Box flexDirection="row" flexGrow={1} paddingX={1}>
|
|
167
|
-
|
|
168
|
-
{/* ── LEFT COLUMN ─────────────────────────────────────── */}
|
|
169
|
-
<Box flexDirection="column" width={leftW}>
|
|
170
|
-
|
|
171
|
-
<SectionHeader title="Totals" />
|
|
172
|
-
<StatRow label="Total tokens" value={formatTokens(stats.totalTokens.total)} />
|
|
173
|
-
<StatRow label=" input" value={formatTokens(stats.totalTokens.input)} color={colors.info} />
|
|
174
|
-
<StatRow label=" output" value={formatTokens(stats.totalTokens.output)} color={colors.cyan} />
|
|
175
|
-
<StatRow label=" cache r/w" value={`${formatTokens(stats.totalTokens.cacheRead)} / ${formatTokens(stats.totalTokens.cacheWrite)}`} color={colors.textDim} />
|
|
176
|
-
<StatRow label="Total cost" value={`$${stats.totalCost.toFixed(4)}`} color={colors.success} />
|
|
177
|
-
|
|
178
|
-
<SectionHeader title="Token trend (7d)" />
|
|
179
|
-
<Box flexDirection="row">
|
|
180
|
-
<SparkLine values={weeklyTokenValues} color={colors.cyan} />
|
|
181
|
-
</Box>
|
|
182
|
-
<Box flexDirection="row">
|
|
183
|
-
<Text color={colors.textDim}>{stats.weeklyTokens[0]?.date.slice(3) ?? ""}</Text>
|
|
184
|
-
<Box flexGrow={1} />
|
|
185
|
-
<Text color={colors.textDim}>peak {formatTokens(maxWeeklyTokens)}</Text>
|
|
186
|
-
<Box flexGrow={1} />
|
|
187
|
-
<Text color={colors.textDim}>{stats.weeklyTokens[6]?.date.slice(3) ?? ""}</Text>
|
|
188
|
-
</Box>
|
|
189
|
-
|
|
190
|
-
<SectionHeader title="Sessions (7d)" />
|
|
191
|
-
<Box flexDirection="row">
|
|
192
|
-
<SparkLine values={weeklySessionValues} color={colors.accent} />
|
|
193
|
-
</Box>
|
|
194
|
-
<Box flexDirection="row">
|
|
195
|
-
<Text color={colors.textDim}>{stats.weeklySessions[0]?.date.slice(3) ?? ""}</Text>
|
|
196
|
-
<Box flexGrow={1} />
|
|
197
|
-
<Text color={colors.textDim}>peak {maxWeeklySessions}/day</Text>
|
|
198
|
-
<Box flexGrow={1} />
|
|
199
|
-
<Text color={colors.textDim}>{stats.weeklySessions[6]?.date.slice(3) ?? ""}</Text>
|
|
200
|
-
</Box>
|
|
201
|
-
|
|
202
|
-
<SectionHeader title="Hourly activity" />
|
|
203
|
-
<Text color={colors.warning}>{hourSpark}</Text>
|
|
204
|
-
<Box flexDirection="row">
|
|
205
|
-
<Text color={colors.textDim}>00</Text>
|
|
206
|
-
<Box flexGrow={1} />
|
|
207
|
-
<Text color={colors.textDim}>06</Text>
|
|
208
|
-
<Box flexGrow={1} />
|
|
209
|
-
<Text color={colors.textDim}>12</Text>
|
|
210
|
-
<Box flexGrow={1} />
|
|
211
|
-
<Text color={colors.textDim}>18</Text>
|
|
212
|
-
<Box flexGrow={1} />
|
|
213
|
-
<Text color={colors.textDim}>23</Text>
|
|
214
|
-
</Box>
|
|
215
|
-
|
|
216
|
-
</Box>
|
|
217
|
-
|
|
218
|
-
{/* ── MIDDLE COLUMN ───────────────────────────────────── */}
|
|
219
|
-
<Box flexDirection="column" width={midW} paddingLeft={2}>
|
|
220
|
-
|
|
221
|
-
<SectionHeader title="Tool errors by agent" />
|
|
222
|
-
{agentToolErrorList.length === 0
|
|
223
|
-
? <Text color={colors.textDim}>No tool data</Text>
|
|
224
|
-
: agentToolErrorList.map(([agent, data]) => (
|
|
225
|
-
<ErrorBar
|
|
226
|
-
key={agent}
|
|
227
|
-
label={agent}
|
|
228
|
-
calls={data.calls}
|
|
229
|
-
errors={data.errors}
|
|
230
|
-
width={14}
|
|
231
|
-
labelWidth={9}
|
|
232
|
-
/>
|
|
233
|
-
))
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
<SectionHeader title="Top tools (calls / errors)" />
|
|
237
|
-
{topTools.length === 0
|
|
238
|
-
? <Text color={colors.textDim}>No tool data</Text>
|
|
239
|
-
: topTools.map(([name, data]) => (
|
|
240
|
-
<Box key={name} flexDirection="row">
|
|
241
|
-
<Box width={10}>
|
|
242
|
-
<Text color={colors.text}>{truncate(name, 9)}</Text>
|
|
243
|
-
</Box>
|
|
244
|
-
<Text color={data.errors > 0 ? colors.warning : colors.info}>
|
|
245
|
-
{"█".repeat(Math.max(1, Math.round((data.calls / maxToolCalls) * 12)))}
|
|
246
|
-
</Text>
|
|
247
|
-
<Text color={colors.border}>
|
|
248
|
-
{"░".repeat(Math.max(0, 12 - Math.max(1, Math.round((data.calls / maxToolCalls) * 12))))}
|
|
249
|
-
</Text>
|
|
250
|
-
<Text color={colors.textDim}> {data.calls}</Text>
|
|
251
|
-
{data.errors > 0 && <Text color={colors.error}> ✗{data.errors}</Text>}
|
|
252
|
-
</Box>
|
|
253
|
-
))
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
</Box>
|
|
257
|
-
|
|
258
|
-
{/* ── RIGHT COLUMN ────────────────────────────────────── */}
|
|
259
|
-
<Box flexDirection="column" flexGrow={1} paddingLeft={2}>
|
|
260
|
-
|
|
261
|
-
<SectionHeader title="Projects" />
|
|
262
|
-
{topProjects.length === 0
|
|
263
|
-
? <Text color={colors.textDim}>No project data</Text>
|
|
264
|
-
: topProjects.map(([project, data]) => (
|
|
265
|
-
<Box key={project} flexDirection="row">
|
|
266
|
-
<Box width={22}>
|
|
267
|
-
<Text color={colors.text}>{truncate(project, 20)}</Text>
|
|
268
|
-
</Box>
|
|
269
|
-
<Text color={colors.info}>{data.sessions} sess</Text>
|
|
270
|
-
<Text color={colors.success}> ${data.cost.toFixed(3)}</Text>
|
|
271
|
-
</Box>
|
|
272
|
-
))
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
<SectionHeader title="Tool avg duration (top 5)" />
|
|
276
|
-
{(() => {
|
|
277
|
-
const durList = Array.from(stats.toolCallCounts.entries())
|
|
278
|
-
.filter(([, d]) => d.calls > 0 && d.totalDurationMs > 0)
|
|
279
|
-
.map(([name, d]) => ({ name, avg: d.totalDurationMs / d.calls }))
|
|
280
|
-
.sort((a, b) => b.avg - a.avg)
|
|
281
|
-
.slice(0, 5);
|
|
282
|
-
const maxAvg = durList[0]?.avg ?? 1;
|
|
283
|
-
const barW = 10;
|
|
284
|
-
return durList.map(({ name, avg }) => {
|
|
285
|
-
const filled = Math.max(1, Math.round((avg / maxAvg) * barW));
|
|
286
|
-
const durStr = avg < 1000 ? `${avg.toFixed(0)}ms` : `${(avg / 1000).toFixed(1)}s`;
|
|
287
|
-
return (
|
|
288
|
-
<Box key={name} flexDirection="row">
|
|
289
|
-
<Box width={12}>
|
|
290
|
-
<Text color={colors.text}>{truncate(name, 10)}</Text>
|
|
291
|
-
</Box>
|
|
292
|
-
<Text color={colors.warning}>{"█".repeat(filled)}</Text>
|
|
293
|
-
<Text color={colors.border}>{"░".repeat(barW - filled)}</Text>
|
|
294
|
-
<Text color={colors.textDim}> {durStr}</Text>
|
|
295
|
-
</Box>
|
|
296
|
-
);
|
|
297
|
-
});
|
|
298
|
-
})()}
|
|
299
|
-
|
|
300
|
-
<SectionHeader title="Models" />
|
|
301
|
-
{topModels.length === 0
|
|
302
|
-
? <Text color={colors.textDim}>No model data</Text>
|
|
303
|
-
: topModels.map(([model, data]) => (
|
|
304
|
-
<Box key={model} flexDirection="row">
|
|
305
|
-
<Box width={22}>
|
|
306
|
-
<Text color={colors.text}>{truncate(model, 20)}</Text>
|
|
307
|
-
</Box>
|
|
308
|
-
<Text color={colors.info}>{data.calls}</Text>
|
|
309
|
-
<Text color={colors.textDim}> {formatTokens(data.tokens)}</Text>
|
|
310
|
-
</Box>
|
|
311
|
-
))
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
</Box>
|
|
315
|
-
</Box>
|
|
316
|
-
|
|
317
|
-
<StatusBar hints="1:sessions 2:tools r:refresh q:quit" />
|
|
318
|
-
</Box>
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export const OverviewScreen = memo(OverviewScreenInner);
|
|
323
|
-
|
|
324
|
-
function truncate(s: string, max: number): string {
|
|
325
|
-
if (s.length <= max) return s;
|
|
326
|
-
return s.slice(0, max - 1) + "…";
|
|
327
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import React, { useState, useMemo, useCallback, memo } from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import { colors } from "../theme";
|
|
4
|
-
import { StatusBar } from "../components/StatusBar";
|
|
5
|
-
import { AgentTree } from "../components/AgentTree";
|
|
6
|
-
import { DetailsPanel } from "../components/DetailsPanel";
|
|
7
|
-
import { MessagesPanel } from "../components/MessagesPanel";
|
|
8
|
-
import type { Workflow, AgentNode, FlatNode } from "../../core/types";
|
|
9
|
-
|
|
10
|
-
interface SessionsScreenProps {
|
|
11
|
-
workflows: Workflow[];
|
|
12
|
-
isActive: boolean;
|
|
13
|
-
contentHeight: number;
|
|
14
|
-
terminalWidth: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type RightMode = "stats" | "messages";
|
|
18
|
-
|
|
19
|
-
function flattenWorkflow(workflow: Workflow, workflowIndex: number): FlatNode[] {
|
|
20
|
-
const nodes: FlatNode[] = [];
|
|
21
|
-
function walk(node: AgentNode) {
|
|
22
|
-
nodes.push({
|
|
23
|
-
id: node.session.id,
|
|
24
|
-
session: node.session,
|
|
25
|
-
workflowIndex,
|
|
26
|
-
depth: node.depth,
|
|
27
|
-
hasChildren: node.children.length > 0,
|
|
28
|
-
agentNode: node,
|
|
29
|
-
});
|
|
30
|
-
for (const child of node.children) walk(child);
|
|
31
|
-
}
|
|
32
|
-
walk(workflow.agentTree);
|
|
33
|
-
return nodes;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function SessionsScreenInner({
|
|
37
|
-
workflows,
|
|
38
|
-
isActive,
|
|
39
|
-
contentHeight,
|
|
40
|
-
terminalWidth,
|
|
41
|
-
}: SessionsScreenProps) {
|
|
42
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
43
|
-
const [rightMode, setRightMode] = useState<RightMode>("stats");
|
|
44
|
-
|
|
45
|
-
const flatNodes = useMemo(() => {
|
|
46
|
-
return workflows.flatMap((w, i) => flattenWorkflow(w, i));
|
|
47
|
-
}, [workflows]);
|
|
48
|
-
|
|
49
|
-
const clampedIndex = Math.min(selectedIndex, Math.max(0, flatNodes.length - 1));
|
|
50
|
-
const selectedNode = flatNodes[clampedIndex] ?? null;
|
|
51
|
-
|
|
52
|
-
const selectedWorkflow = useMemo(() => {
|
|
53
|
-
if (!selectedNode) return null;
|
|
54
|
-
const w = workflows[selectedNode.workflowIndex];
|
|
55
|
-
if (!w) return null;
|
|
56
|
-
if (selectedNode.session.id !== w.mainSession.id) {
|
|
57
|
-
return {
|
|
58
|
-
id: selectedNode.session.id,
|
|
59
|
-
mainSession: selectedNode.session,
|
|
60
|
-
subAgentSessions: selectedNode.agentNode.children.map((c) => c.session),
|
|
61
|
-
agentTree: selectedNode.agentNode,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
return w;
|
|
65
|
-
}, [selectedNode, workflows]);
|
|
66
|
-
|
|
67
|
-
const leftWidth = Math.floor(terminalWidth * 0.35);
|
|
68
|
-
const rightWidth = terminalWidth - leftWidth - 2;
|
|
69
|
-
|
|
70
|
-
const statusBarHeight = 1;
|
|
71
|
-
const borderRows = 2;
|
|
72
|
-
const innerHeight = contentHeight - statusBarHeight - borderRows;
|
|
73
|
-
const panelHeight = contentHeight - statusBarHeight;
|
|
74
|
-
const msgHeight = innerHeight - 1; // minus tab header row
|
|
75
|
-
|
|
76
|
-
const handleSelect = useCallback((index: number) => {
|
|
77
|
-
setSelectedIndex(index);
|
|
78
|
-
}, []);
|
|
79
|
-
|
|
80
|
-
// SessionsScreen only handles: tab switch, tree nav (stats mode), session switch (messages mode)
|
|
81
|
-
useInput(
|
|
82
|
-
(input, key) => {
|
|
83
|
-
if (key.tab) {
|
|
84
|
-
setRightMode((m) => (m === "stats" ? "messages" : "stats"));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (rightMode === "stats") {
|
|
88
|
-
if (key.upArrow || input === "k") { handleSelect(Math.max(0, clampedIndex - 1)); return; }
|
|
89
|
-
if (key.downArrow || input === "j") { handleSelect(Math.min(flatNodes.length - 1, clampedIndex + 1)); return; }
|
|
90
|
-
if (input === "g") { handleSelect(0); return; }
|
|
91
|
-
if (input === "G") { handleSelect(flatNodes.length - 1); return; }
|
|
92
|
-
} else {
|
|
93
|
-
// In messages mode, [ and ] switch session
|
|
94
|
-
if (input === "[") { handleSelect(Math.max(0, clampedIndex - 1)); return; }
|
|
95
|
-
if (input === "]") { handleSelect(Math.min(flatNodes.length - 1, clampedIndex + 1)); return; }
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
{ isActive }
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<Box flexDirection="column" width={terminalWidth} height={contentHeight}>
|
|
103
|
-
<Box flexDirection="row" height={panelHeight}>
|
|
104
|
-
{/* Left: agent/session tree */}
|
|
105
|
-
<Box
|
|
106
|
-
width={leftWidth}
|
|
107
|
-
height={panelHeight}
|
|
108
|
-
borderStyle="single"
|
|
109
|
-
borderColor={colors.border}
|
|
110
|
-
flexDirection="column"
|
|
111
|
-
>
|
|
112
|
-
<AgentTree
|
|
113
|
-
workflows={workflows}
|
|
114
|
-
selectedId={selectedNode?.id ?? null}
|
|
115
|
-
flatNodes={flatNodes}
|
|
116
|
-
onSelect={(id) => {
|
|
117
|
-
const idx = flatNodes.findIndex((n) => n.id === id);
|
|
118
|
-
if (idx >= 0) handleSelect(idx);
|
|
119
|
-
}}
|
|
120
|
-
maxHeight={innerHeight}
|
|
121
|
-
/>
|
|
122
|
-
</Box>
|
|
123
|
-
|
|
124
|
-
{/* Right: details or messages */}
|
|
125
|
-
<Box
|
|
126
|
-
width={rightWidth}
|
|
127
|
-
height={panelHeight}
|
|
128
|
-
borderStyle="single"
|
|
129
|
-
borderColor={colors.border}
|
|
130
|
-
flexDirection="column"
|
|
131
|
-
>
|
|
132
|
-
{/* Tab header: 1 row */}
|
|
133
|
-
<Box paddingX={1} height={1} flexDirection="row">
|
|
134
|
-
<Text color={rightMode === "stats" ? colors.accent : colors.textDim} bold={rightMode === "stats"}>
|
|
135
|
-
[Stats]
|
|
136
|
-
</Text>
|
|
137
|
-
<Text color={colors.textDim}> </Text>
|
|
138
|
-
<Text color={rightMode === "messages" ? colors.accent : colors.textDim} bold={rightMode === "messages"}>
|
|
139
|
-
[Messages]
|
|
140
|
-
</Text>
|
|
141
|
-
<Box flexGrow={1} />
|
|
142
|
-
<Text color={colors.textDim}>Tab:switch</Text>
|
|
143
|
-
</Box>
|
|
144
|
-
|
|
145
|
-
{rightMode === "stats" ? (
|
|
146
|
-
<DetailsPanel workflow={selectedWorkflow} height={innerHeight - 1} />
|
|
147
|
-
) : (
|
|
148
|
-
<MessagesPanel
|
|
149
|
-
session={selectedNode?.session ?? null}
|
|
150
|
-
maxHeight={msgHeight}
|
|
151
|
-
isActive={isActive && rightMode === "messages"}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
154
|
-
</Box>
|
|
155
|
-
</Box>
|
|
156
|
-
|
|
157
|
-
<StatusBar
|
|
158
|
-
hints={
|
|
159
|
-
rightMode === "messages"
|
|
160
|
-
? "j/k:scroll d/u:½page g/G:top/bot Enter:expand [:prev ]:next Tab:stats q:quit"
|
|
161
|
-
: "j/k:nav g/G:top/bot Tab:messages 2:tools 3:overview r:refresh q:quit"
|
|
162
|
-
}
|
|
163
|
-
/>
|
|
164
|
-
</Box>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export const SessionsScreen = memo(SessionsScreenInner);
|