wave-code 1.3.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 +1275 -895
- package/package.json +4 -2
- package/src/commands/update.ts +62 -22
- package/src/components/BackgroundTaskManager.tsx +76 -54
- package/src/components/ConfirmationDetails.tsx +17 -3
- package/src/components/InputBox.tsx +2 -0
- package/src/components/MessageBlockItem.tsx +0 -6
- package/src/components/WorkflowManager.tsx +38 -138
- package/src/components/WorkflowRunDetail.tsx +193 -0
- package/src/constants/commands.ts +6 -0
- package/src/contexts/useChat.tsx +359 -127
- package/src/daemon/commands.ts +13 -4
- package/src/daemon-cli.ts +6 -1
- package/src/hooks/useInputManager.ts +4 -0
- package/src/hooks/useRemoteControl.ts +324 -0
- package/src/index.ts +35 -0
- package/src/managers/inputHandlers.ts +2 -0
- package/src/managers/inputReducer.ts +1 -0
- package/src/print-cli.ts +5 -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/stdio/agentBridge.ts +608 -74
- package/src/stdio/daemonServer.ts +30 -0
- package/src/stdio/jsonRpcConnection.ts +11 -2
- package/src/stdio/protocol.ts +14 -115
- package/src/stdio/stdioServer.ts +33 -2
- package/src/stdio-cli.ts +11 -2
- package/src/utils/gitCredentialHelper.ts +106 -0
- package/src/utils/logger.ts +5 -2
- package/src/utils/rewindCheckpoints.ts +2 -4
- package/src/utils/workspaceDiff.ts +620 -0
- package/src/utils/worktree.ts +97 -0
|
@@ -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;
|
|
@@ -110,6 +110,12 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
|
|
|
110
110
|
description: "Set a custom title for the current conversation",
|
|
111
111
|
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
112
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
|
+
},
|
|
113
119
|
{
|
|
114
120
|
id: "clear",
|
|
115
121
|
name: "clear",
|