wave-code 1.2.0 → 1.4.0
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/dist/bundle/wave.mjs +1738 -893
- package/package.json +6 -3
- package/src/cli.tsx +9 -0
- package/src/commands/plugin/install.ts +6 -7
- package/src/commands/plugin/uninstall.ts +11 -5
- package/src/commands/update.ts +62 -22
- package/src/components/BackgroundTaskManager.tsx +76 -54
- package/src/components/ChatInterface.tsx +1 -0
- package/src/components/ConfirmationDetails.tsx +51 -16
- package/src/components/ConfirmationSelector.tsx +39 -0
- package/src/components/InputBox.tsx +41 -2
- package/src/components/Markdown.tsx +21 -8
- package/src/components/MarketplaceDetail.tsx +10 -27
- package/src/components/MarketplaceView.tsx +17 -3
- package/src/components/MessageBlockItem.tsx +0 -6
- package/src/components/Notifications.tsx +7 -0
- package/src/components/PlanView.tsx +22 -17
- package/src/components/PluginManagerShell.tsx +3 -3
- package/src/components/PluginManagerTypes.ts +2 -1
- package/src/components/ResumeCommand.tsx +84 -0
- package/src/components/ScrolledContent.tsx +71 -0
- package/src/components/SessionSelector.tsx +184 -6
- package/src/components/SessionSelectorFlow.tsx +246 -0
- package/src/components/WorkflowManager.tsx +38 -138
- package/src/components/WorkflowRunDetail.tsx +193 -0
- package/src/constants/commands.ts +24 -0
- package/src/contexts/useChat.tsx +517 -155
- package/src/daemon/commands.ts +582 -50
- package/src/daemon-cli.ts +12 -1
- package/src/hooks/useInputManager.ts +20 -0
- package/src/hooks/usePluginManager.ts +104 -78
- package/src/hooks/useRemoteControl.ts +324 -0
- package/src/index.ts +97 -8
- package/src/managers/inputHandlers.ts +16 -9
- package/src/managers/inputReducer.ts +30 -9
- package/src/print-cli.ts +5 -0
- package/src/reducers/confirmationReducer.ts +20 -1
- package/src/reducers/selectorReducer.ts +3 -0
- package/src/reducers/workflowManagerReducer.ts +21 -2
- package/src/remote/frameBudget.ts +45 -0
- package/src/remote/hostIdentity.ts +128 -0
- package/src/remote/messageWindow.ts +97 -0
- package/src/remote/notificationCoalescer.ts +174 -0
- package/src/remote/relayClient.ts +668 -0
- package/src/remote/remoteControlCliHost.ts +391 -0
- package/src/remote/remoteControlCommands.ts +197 -0
- package/src/remote/remoteControlConfig.ts +334 -0
- package/src/remote/remoteControlService.ts +799 -0
- package/src/remote/remoteControlSink.ts +77 -0
- package/src/remote/remoteNotifications.ts +194 -0
- package/src/session-selector-cli.tsx +9 -255
- package/src/stdio/agentBridge.ts +1001 -165
- package/src/stdio/daemonServer.ts +30 -0
- package/src/stdio/jsonRpcConnection.ts +11 -2
- package/src/stdio/protocol.ts +14 -106
- package/src/stdio/stdioServer.ts +33 -2
- package/src/stdio-cli.ts +16 -1
- package/src/utils/gitCredentialHelper.ts +106 -0
- package/src/utils/logger.ts +5 -2
- package/src/utils/rewindCheckpoints.ts +2 -4
- package/src/utils/sessionOwnership.ts +62 -0
- package/src/utils/workspaceDiff.ts +620 -0
- package/src/utils/worktree.ts +97 -0
- package/src/components/PlanDisplay.tsx +0 -18
|
@@ -1,49 +1,20 @@
|
|
|
1
1
|
import React, { useEffect, useReducer } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { useChat } from "../contexts/useChat.js";
|
|
4
|
-
import type { WorkflowRun } from "wave-agent-sdk";
|
|
5
4
|
import {
|
|
6
5
|
workflowManagerReducer,
|
|
7
6
|
type WorkflowManagerState,
|
|
8
7
|
} from "../reducers/workflowManagerReducer.js";
|
|
8
|
+
import WorkflowRunDetail, {
|
|
9
|
+
formatDuration,
|
|
10
|
+
formatTokens,
|
|
11
|
+
workflowStatusColor,
|
|
12
|
+
} from "./WorkflowRunDetail.js";
|
|
9
13
|
|
|
10
14
|
export interface WorkflowManagerProps {
|
|
11
15
|
onCancel: () => void;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
const statusColor = (
|
|
15
|
-
status: WorkflowRun["status"],
|
|
16
|
-
): "green" | "blue" | "red" | "yellow" => {
|
|
17
|
-
switch (status) {
|
|
18
|
-
case "running":
|
|
19
|
-
return "green";
|
|
20
|
-
case "completed":
|
|
21
|
-
return "blue";
|
|
22
|
-
case "failed":
|
|
23
|
-
case "aborted":
|
|
24
|
-
return "red";
|
|
25
|
-
case "paused":
|
|
26
|
-
return "yellow";
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const formatDuration = (ms: number): string => {
|
|
31
|
-
if (ms < 1000) return `${ms}ms`;
|
|
32
|
-
if (ms < 60000) return `${Math.round(ms / 1000)}s`;
|
|
33
|
-
const minutes = Math.floor(ms / 60000);
|
|
34
|
-
const seconds = Math.round((ms % 60000) / 1000);
|
|
35
|
-
return `${minutes}m ${seconds}s`;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const formatTime = (timestamp: number): string => {
|
|
39
|
-
return new Date(timestamp).toLocaleTimeString();
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const formatTokens = (tokens: number): string => {
|
|
43
|
-
if (tokens < 1000) return `${tokens}`;
|
|
44
|
-
return `${(tokens / 1000).toFixed(1)}k`;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
18
|
export const WorkflowManager: React.FC<WorkflowManagerProps> = ({
|
|
48
19
|
onCancel,
|
|
49
20
|
}) => {
|
|
@@ -94,10 +65,6 @@ export const WorkflowManager: React.FC<WorkflowManagerProps> = ({
|
|
|
94
65
|
return null;
|
|
95
66
|
}
|
|
96
67
|
|
|
97
|
-
const elapsed = run.endTime
|
|
98
|
-
? run.endTime - run.startTime
|
|
99
|
-
: Date.now() - run.startTime;
|
|
100
|
-
|
|
101
68
|
return (
|
|
102
69
|
<Box
|
|
103
70
|
flexDirection="column"
|
|
@@ -115,75 +82,7 @@ export const WorkflowManager: React.FC<WorkflowManagerProps> = ({
|
|
|
115
82
|
</Text>
|
|
116
83
|
</Box>
|
|
117
84
|
|
|
118
|
-
<
|
|
119
|
-
<Box>
|
|
120
|
-
<Text>
|
|
121
|
-
<Text color="blue">Run ID:</Text> {run.runId}
|
|
122
|
-
</Text>
|
|
123
|
-
</Box>
|
|
124
|
-
{run.meta.description && (
|
|
125
|
-
<Box>
|
|
126
|
-
<Text>
|
|
127
|
-
<Text color="blue">Description:</Text> {run.meta.description}
|
|
128
|
-
</Text>
|
|
129
|
-
</Box>
|
|
130
|
-
)}
|
|
131
|
-
<Box>
|
|
132
|
-
<Text>
|
|
133
|
-
<Text color="blue">Status:</Text>{" "}
|
|
134
|
-
<Text color={statusColor(run.status)}>{run.status}</Text>
|
|
135
|
-
</Text>
|
|
136
|
-
</Box>
|
|
137
|
-
<Box>
|
|
138
|
-
<Text>
|
|
139
|
-
<Text color="blue">Started:</Text> {formatTime(run.startTime)}
|
|
140
|
-
{run.endTime && (
|
|
141
|
-
<Text>
|
|
142
|
-
{" "}
|
|
143
|
-
| <Text color="blue">Ended:</Text> {formatTime(run.endTime)}
|
|
144
|
-
</Text>
|
|
145
|
-
)}
|
|
146
|
-
{" | "}
|
|
147
|
-
<Text color="blue">Duration:</Text> {formatDuration(elapsed)}
|
|
148
|
-
</Text>
|
|
149
|
-
</Box>
|
|
150
|
-
<Box>
|
|
151
|
-
<Text>
|
|
152
|
-
<Text color="blue">Agents:</Text> {run.totalAgents} {" | "}
|
|
153
|
-
<Text color="blue">Tokens:</Text> {formatTokens(run.totalTokens)}
|
|
154
|
-
</Text>
|
|
155
|
-
</Box>
|
|
156
|
-
<Box>
|
|
157
|
-
<Text>
|
|
158
|
-
<Text color="blue">Script:</Text> {run.scriptPath}
|
|
159
|
-
</Text>
|
|
160
|
-
</Box>
|
|
161
|
-
{run.error && (
|
|
162
|
-
<Box>
|
|
163
|
-
<Text color="red">
|
|
164
|
-
<Text color="blue">Error:</Text> {run.error}
|
|
165
|
-
</Text>
|
|
166
|
-
</Box>
|
|
167
|
-
)}
|
|
168
|
-
</Box>
|
|
169
|
-
|
|
170
|
-
{run.phases.length > 0 && (
|
|
171
|
-
<Box flexDirection="column" marginTop={1}>
|
|
172
|
-
<Text color="cyan" bold>
|
|
173
|
-
Phases:
|
|
174
|
-
</Text>
|
|
175
|
-
{run.phases.map((phase, i) => (
|
|
176
|
-
<Box key={i} marginLeft={2}>
|
|
177
|
-
<Text>
|
|
178
|
-
<Text color="blue">{phase.title}</Text>
|
|
179
|
-
{" — "}
|
|
180
|
-
{phase.agentCount} agents | {formatTokens(phase.tokens)}{" "}
|
|
181
|
-
tokens | {formatDuration(phase.elapsed)}
|
|
182
|
-
</Text>
|
|
183
|
-
</Box>
|
|
184
|
-
))}
|
|
185
|
-
</Box>
|
|
186
|
-
)}
|
|
85
|
+
<WorkflowRunDetail run={run} />
|
|
187
86
|
|
|
188
87
|
<Box marginTop={1}>
|
|
189
88
|
<Text dimColor>
|
|
@@ -225,6 +124,17 @@ export const WorkflowManager: React.FC<WorkflowManagerProps> = ({
|
|
|
225
124
|
);
|
|
226
125
|
const visibleRuns = runs.slice(startIndex, startIndex + MAX_VISIBLE_ITEMS);
|
|
227
126
|
|
|
127
|
+
// Header counts of the states a user acts on (a failed run is plain to see in
|
|
128
|
+
// its own row, so it needs no tally).
|
|
129
|
+
const runningCount = runs.filter((r) => r.status === "running").length;
|
|
130
|
+
const completedCount = runs.filter((r) => r.status === "completed").length;
|
|
131
|
+
const subtitle = [
|
|
132
|
+
runningCount > 0 ? `${runningCount} running` : "",
|
|
133
|
+
completedCount > 0 ? `${completedCount} completed` : "",
|
|
134
|
+
]
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.join(" · ");
|
|
137
|
+
|
|
228
138
|
return (
|
|
229
139
|
<Box
|
|
230
140
|
flexDirection="column"
|
|
@@ -240,6 +150,7 @@ export const WorkflowManager: React.FC<WorkflowManagerProps> = ({
|
|
|
240
150
|
<Text color="cyan" bold>
|
|
241
151
|
Workflows
|
|
242
152
|
</Text>
|
|
153
|
+
{subtitle && <Text dimColor> {subtitle}</Text>}
|
|
243
154
|
</Box>
|
|
244
155
|
<Text dimColor>Select a run to view details</Text>
|
|
245
156
|
|
|
@@ -250,7 +161,15 @@ export const WorkflowManager: React.FC<WorkflowManagerProps> = ({
|
|
|
250
161
|
const elapsed = run.endTime
|
|
251
162
|
? run.endTime - run.startTime
|
|
252
163
|
: Date.now() - run.startTime;
|
|
253
|
-
|
|
164
|
+
// Duration always reads; the two counters are dropped at zero so an
|
|
165
|
+
// unstarted run does not claim "0 agents · 0 tokens".
|
|
166
|
+
const meta = [
|
|
167
|
+
formatDuration(elapsed),
|
|
168
|
+
run.totalAgents > 0 ? `${run.totalAgents} agents` : "",
|
|
169
|
+
run.totalTokens > 0
|
|
170
|
+
? `${formatTokens(run.totalTokens)} tokens`
|
|
171
|
+
: "",
|
|
172
|
+
].filter(Boolean);
|
|
254
173
|
|
|
255
174
|
return (
|
|
256
175
|
<Box key={run.runId} flexDirection="column">
|
|
@@ -259,43 +178,24 @@ export const WorkflowManager: React.FC<WorkflowManagerProps> = ({
|
|
|
259
178
|
backgroundColor={isSelected ? "cyan" : undefined}
|
|
260
179
|
>
|
|
261
180
|
{isSelected ? "▶ " : " "}
|
|
262
|
-
{actualIndex + 1}. {run.
|
|
263
|
-
<Text color={
|
|
181
|
+
{actualIndex + 1}. {run.meta.name || run.meta.description}
|
|
182
|
+
<Text color={workflowStatusColor(run.status)}>
|
|
183
|
+
{" "}
|
|
184
|
+
({run.status})
|
|
185
|
+
</Text>
|
|
186
|
+
{/* Oversized marker: the row is where a run is picked, so the
|
|
187
|
+
"too big to leave running" signal belongs on it (spec
|
|
188
|
+
「预算与规模硬上限」场景 7). The counts are on the meta line
|
|
189
|
+
right below, and the stop key is in the footer. */}
|
|
190
|
+
{run.sizeWarning && <Text color="yellow"> ⚠ Large</Text>}
|
|
264
191
|
</Text>
|
|
265
192
|
<Text
|
|
266
193
|
color={isSelected ? "black" : "gray"}
|
|
267
194
|
backgroundColor={isSelected ? "cyan" : undefined}
|
|
268
195
|
>
|
|
269
196
|
{" "}
|
|
270
|
-
{
|
|
271
|
-
tokens · {formatDuration(elapsed)}
|
|
197
|
+
{meta.join(" · ")}
|
|
272
198
|
</Text>
|
|
273
|
-
{phaseText && (
|
|
274
|
-
<Text
|
|
275
|
-
color={isSelected ? "black" : "gray"}
|
|
276
|
-
backgroundColor={isSelected ? "cyan" : undefined}
|
|
277
|
-
>
|
|
278
|
-
{" "}
|
|
279
|
-
{run.phases.map((p, pi) => (
|
|
280
|
-
<React.Fragment key={pi}>
|
|
281
|
-
{pi > 0 && " → "}
|
|
282
|
-
<Text
|
|
283
|
-
color={
|
|
284
|
-
isSelected
|
|
285
|
-
? "black"
|
|
286
|
-
: pi === run.phases.length - 1 &&
|
|
287
|
-
run.status === "running"
|
|
288
|
-
? "green"
|
|
289
|
-
: undefined
|
|
290
|
-
}
|
|
291
|
-
backgroundColor={isSelected ? "cyan" : undefined}
|
|
292
|
-
>
|
|
293
|
-
{p.title}
|
|
294
|
-
</Text>
|
|
295
|
-
</React.Fragment>
|
|
296
|
-
))}
|
|
297
|
-
</Text>
|
|
298
|
-
)}
|
|
299
199
|
{isSelected && (
|
|
300
200
|
<Box marginLeft={4} flexDirection="column">
|
|
301
201
|
<Text color="gray" dimColor>
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkflowRunDetail - one workflow run's own fields, rendered for the terminal.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the two CLI panels that inspect a run: the `/workflows` detail view,
|
|
5
|
+
* and the `/tasks` detail view when the selected task is a workflow (a workflow
|
|
6
|
+
* background task's id IS its run id, so the two panels join on it). Both render
|
|
7
|
+
* this component, so they cannot disagree about what a run's progress looks like.
|
|
8
|
+
*
|
|
9
|
+
* The information set matches what the webview renders through
|
|
10
|
+
* `WorkflowRunProgress` — same fields, same order, same zero-value rules — but
|
|
11
|
+
* the carrier differs: Ink text lines instead of a DOM block, and the log lines
|
|
12
|
+
* are capped because an Ink panel does not scroll. That split is the spec's
|
|
13
|
+
* cross-renderer rule (docs/specs/multi-agent/workflow.md 边界情况).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React from "react";
|
|
17
|
+
import { Box, Text } from "ink";
|
|
18
|
+
import { getLastLines } from "wave-agent-sdk";
|
|
19
|
+
import type { WorkflowRun } from "wave-agent-sdk";
|
|
20
|
+
import {
|
|
21
|
+
WORKFLOW_SIZE_WARNING_AGENTS,
|
|
22
|
+
WORKFLOW_SIZE_WARNING_TOKENS,
|
|
23
|
+
} from "wave-agent-sdk/constants";
|
|
24
|
+
|
|
25
|
+
export const formatDuration = (ms: number): string => {
|
|
26
|
+
if (ms < 1000) return `${ms}ms`;
|
|
27
|
+
if (ms < 60000) return `${Math.round(ms / 1000)}s`;
|
|
28
|
+
const minutes = Math.floor(ms / 60000);
|
|
29
|
+
const seconds = Math.round((ms % 60000) / 1000);
|
|
30
|
+
return `${minutes}m ${seconds}s`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const formatTime = (timestamp: number): string => {
|
|
34
|
+
return new Date(timestamp).toLocaleTimeString();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const formatTokens = (tokens: number): string => {
|
|
38
|
+
if (tokens < 1000) return `${tokens}`;
|
|
39
|
+
// Millions matter here: a run crosses the 1.5M size threshold already showing
|
|
40
|
+
// "1500.0k", and the marker line names that threshold.
|
|
41
|
+
if (tokens < 1_000_000) return `${(tokens / 1000).toFixed(1)}k`;
|
|
42
|
+
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const workflowStatusColor = (
|
|
46
|
+
status: WorkflowRun["status"],
|
|
47
|
+
): "green" | "blue" | "red" | "yellow" => {
|
|
48
|
+
switch (status) {
|
|
49
|
+
case "running":
|
|
50
|
+
return "green";
|
|
51
|
+
case "completed":
|
|
52
|
+
return "blue";
|
|
53
|
+
case "failed":
|
|
54
|
+
case "aborted":
|
|
55
|
+
return "red";
|
|
56
|
+
case "paused":
|
|
57
|
+
return "yellow";
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Log lines rendered. Same convention as this panel's OUTPUT block ("last 10
|
|
63
|
+
* lines"): a TUI panel cannot scroll, and a workflow that logs inside a loop can
|
|
64
|
+
* hold hundreds of retained lines.
|
|
65
|
+
*/
|
|
66
|
+
export const MAX_RUN_LOG_LINES = 10;
|
|
67
|
+
|
|
68
|
+
const WorkflowRunDetail: React.FC<{ run: WorkflowRun }> = ({ run }) => {
|
|
69
|
+
const elapsed = run.endTime
|
|
70
|
+
? run.endTime - run.startTime
|
|
71
|
+
: Date.now() - run.startTime;
|
|
72
|
+
const logs = run.logs ?? [];
|
|
73
|
+
const failures = run.failures ?? [];
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<>
|
|
77
|
+
<Box flexDirection="column" gap={0}>
|
|
78
|
+
<Box>
|
|
79
|
+
<Text>
|
|
80
|
+
<Text color="blue">Run ID:</Text> {run.runId}
|
|
81
|
+
</Text>
|
|
82
|
+
</Box>
|
|
83
|
+
{run.meta.description && (
|
|
84
|
+
<Box>
|
|
85
|
+
<Text>
|
|
86
|
+
<Text color="blue">Description:</Text> {run.meta.description}
|
|
87
|
+
</Text>
|
|
88
|
+
</Box>
|
|
89
|
+
)}
|
|
90
|
+
<Box>
|
|
91
|
+
<Text>
|
|
92
|
+
<Text color="blue">Status:</Text>{" "}
|
|
93
|
+
<Text color={workflowStatusColor(run.status)}>{run.status}</Text>
|
|
94
|
+
</Text>
|
|
95
|
+
</Box>
|
|
96
|
+
<Box>
|
|
97
|
+
<Text>
|
|
98
|
+
<Text color="blue">Started:</Text> {formatTime(run.startTime)}
|
|
99
|
+
{run.endTime && (
|
|
100
|
+
<Text>
|
|
101
|
+
{" "}
|
|
102
|
+
| <Text color="blue">Ended:</Text> {formatTime(run.endTime)}
|
|
103
|
+
</Text>
|
|
104
|
+
)}
|
|
105
|
+
{" | "}
|
|
106
|
+
<Text color="blue">Duration:</Text> {formatDuration(elapsed)}
|
|
107
|
+
</Text>
|
|
108
|
+
</Box>
|
|
109
|
+
<Box>
|
|
110
|
+
<Text>
|
|
111
|
+
<Text color="blue">Agents:</Text> {run.totalAgents} {" | "}
|
|
112
|
+
<Text color="blue">Tokens:</Text> {formatTokens(run.totalTokens)}
|
|
113
|
+
</Text>
|
|
114
|
+
</Box>
|
|
115
|
+
{/* Oversized marker (spec「预算与规模硬上限」场景 7): the run is bigger
|
|
116
|
+
than the thresholds, which is the human's cue to stop it — the model
|
|
117
|
+
is never told, so this line has to carry the scale and the
|
|
118
|
+
thresholds itself. Stopping stays with this panel's own affordance
|
|
119
|
+
(the footer's `k`), so the line only names it when the run is still
|
|
120
|
+
running and there is something to stop. */}
|
|
121
|
+
{run.sizeWarning && (
|
|
122
|
+
<Box>
|
|
123
|
+
<Text color="yellow">
|
|
124
|
+
⚠ Large run: {run.totalAgents} agents /{" "}
|
|
125
|
+
{formatTokens(run.totalTokens)} tokens — over the{" "}
|
|
126
|
+
{WORKFLOW_SIZE_WARNING_AGENTS} agents /{" "}
|
|
127
|
+
{formatTokens(WORKFLOW_SIZE_WARNING_TOKENS)} tokens threshold.
|
|
128
|
+
{run.status === "running" && " Press k to stop it."}
|
|
129
|
+
</Text>
|
|
130
|
+
</Box>
|
|
131
|
+
)}
|
|
132
|
+
<Box>
|
|
133
|
+
<Text>
|
|
134
|
+
<Text color="blue">Script:</Text> {run.scriptPath}
|
|
135
|
+
</Text>
|
|
136
|
+
</Box>
|
|
137
|
+
{run.error && (
|
|
138
|
+
<Box>
|
|
139
|
+
<Text color="red">
|
|
140
|
+
<Text color="blue">Error:</Text> {run.error}
|
|
141
|
+
</Text>
|
|
142
|
+
</Box>
|
|
143
|
+
)}
|
|
144
|
+
</Box>
|
|
145
|
+
|
|
146
|
+
{failures.length > 0 && (
|
|
147
|
+
<Box flexDirection="column" marginTop={1}>
|
|
148
|
+
<Text color="red" bold>
|
|
149
|
+
Failures:
|
|
150
|
+
</Text>
|
|
151
|
+
{failures.map((failure, i) => (
|
|
152
|
+
<Box key={i} marginLeft={2}>
|
|
153
|
+
<Text color="red">{failure}</Text>
|
|
154
|
+
</Box>
|
|
155
|
+
))}
|
|
156
|
+
</Box>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{/* Only render a section that has content: an empty "Phases:" / "Logs:"
|
|
160
|
+
block reads as missing data rather than "nothing happened yet". */}
|
|
161
|
+
{run.phases.length > 0 && (
|
|
162
|
+
<Box flexDirection="column" marginTop={1}>
|
|
163
|
+
<Text color="cyan" bold>
|
|
164
|
+
Phases:
|
|
165
|
+
</Text>
|
|
166
|
+
{run.phases.map((phase, i) => (
|
|
167
|
+
<Box key={i} marginLeft={2}>
|
|
168
|
+
<Text>
|
|
169
|
+
<Text color="blue">{phase.title}</Text>
|
|
170
|
+
{" — "}
|
|
171
|
+
{phase.agentCount} agents | {formatTokens(phase.tokens)} tokens
|
|
172
|
+
| {formatDuration(phase.elapsed)}
|
|
173
|
+
</Text>
|
|
174
|
+
</Box>
|
|
175
|
+
))}
|
|
176
|
+
</Box>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{logs.length > 0 && (
|
|
180
|
+
<Box flexDirection="column" marginTop={1}>
|
|
181
|
+
<Text color="cyan" bold>
|
|
182
|
+
Logs (last {MAX_RUN_LOG_LINES} lines):
|
|
183
|
+
</Text>
|
|
184
|
+
<Box marginLeft={2}>
|
|
185
|
+
<Text>{getLastLines(logs.join("\n"), MAX_RUN_LOG_LINES)}</Text>
|
|
186
|
+
</Box>
|
|
187
|
+
</Box>
|
|
188
|
+
)}
|
|
189
|
+
</>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export default WorkflowRunDetail;
|
|
@@ -56,6 +56,12 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
|
|
|
56
56
|
description: "View and manage plugins",
|
|
57
57
|
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
id: "reload-plugins",
|
|
61
|
+
name: "reload-plugins",
|
|
62
|
+
description: "Reload plugins in place, without restarting the session",
|
|
63
|
+
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
64
|
+
},
|
|
59
65
|
{
|
|
60
66
|
id: "btw",
|
|
61
67
|
name: "btw",
|
|
@@ -92,6 +98,24 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
|
|
|
92
98
|
description: "Enable plan mode or view the current session plan",
|
|
93
99
|
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
94
100
|
},
|
|
101
|
+
{
|
|
102
|
+
id: "resume",
|
|
103
|
+
name: "resume",
|
|
104
|
+
description: "Switch to another conversation without leaving the session",
|
|
105
|
+
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "rename",
|
|
109
|
+
name: "rename",
|
|
110
|
+
description: "Set a custom title for the current conversation",
|
|
111
|
+
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "remote-control",
|
|
115
|
+
name: "remote-control",
|
|
116
|
+
description: "Serve this terminal's session to a phone",
|
|
117
|
+
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
118
|
+
},
|
|
95
119
|
{
|
|
96
120
|
id: "clear",
|
|
97
121
|
name: "clear",
|