wave-code 1.5.1 → 1.6.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/bin/wave-code.js +2 -0
- package/dist/bundle/wave.mjs +697 -662
- package/package.json +7 -7
- package/src/cli.tsx +9 -3
- package/src/components/McpManager.tsx +26 -0
- package/src/components/MessageBlockItem.tsx +14 -2
- package/src/components/RemoteControlView.tsx +1 -1
- package/src/contexts/useChat.tsx +47 -33
- package/src/daemon/commands.ts +266 -44
- package/src/daemon/daemonLog.ts +132 -0
- package/src/daemon/socketPath.ts +123 -0
- package/src/daemon-cli.ts +19 -6
- package/src/index.ts +6 -0
- package/src/print-cli.ts +20 -10
- package/src/remote/hostIdentity.ts +1 -0
- package/src/remote/imagePreview.ts +182 -0
- package/src/remote/messageWindow.ts +5 -4
- package/src/remote/notificationCoalescer.ts +2 -2
- package/src/remote/relayClient.ts +1 -1
- package/src/remote/remoteControlCliHost.ts +26 -4
- package/src/remote/remoteControlService.ts +67 -16
- package/src/remote/remoteNotifications.ts +5 -4
- package/src/remote/textPreview.ts +155 -0
- package/src/stdio/agentBridge.ts +300 -65
- package/src/stdio/daemonServer.ts +49 -33
- package/src/stdio/hostSlashCommands.ts +9 -4
- package/src/stdio/uiPayload.ts +49 -0
- package/src/utils/argvCompat.ts +23 -0
- package/src/utils/printOutput.ts +1 -1
- package/src/utils/worktree.ts +222 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"wrap-ansi": "^10.0.0",
|
|
60
60
|
"yargs": "^17.7.2",
|
|
61
61
|
"zod": "^3.23.8",
|
|
62
|
-
"wave-agent-sdk": "1.
|
|
62
|
+
"wave-agent-sdk": "1.6.0"
|
|
63
63
|
},
|
|
64
64
|
"engines": {
|
|
65
65
|
"node": ">=22"
|
|
@@ -69,11 +69,11 @@
|
|
|
69
69
|
"wave": "tsx --tsconfig tsconfig.dev.json src/index.ts",
|
|
70
70
|
"build": "rimraf dist && node scripts/bundle.mjs",
|
|
71
71
|
"type-check": "tsc --noEmit --incremental",
|
|
72
|
-
"test": "vitest run
|
|
73
|
-
"test:coverage": "vitest run --coverage
|
|
74
|
-
"test:unit": "vitest run --
|
|
75
|
-
"test:unit:coverage": "vitest run --coverage --
|
|
76
|
-
"test:integration": "vitest run
|
|
72
|
+
"test": "vitest run",
|
|
73
|
+
"test:coverage": "vitest run --coverage",
|
|
74
|
+
"test:unit": "vitest run --exclude 'tests/integration/**' --exclude '**/*.integration.test.ts'",
|
|
75
|
+
"test:unit:coverage": "vitest run --coverage --exclude 'tests/integration/**' --exclude '**/*.integration.test.ts'",
|
|
76
|
+
"test:integration": "vitest run tests/integration .integration.test",
|
|
77
77
|
"lint": "oxlint",
|
|
78
78
|
"format": "prettier --write ."
|
|
79
79
|
}
|
package/src/cli.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { render } from "ink";
|
|
|
3
3
|
import { ensureRuntimeDeps } from "wave-agent-sdk";
|
|
4
4
|
import { App } from "./components/App.js";
|
|
5
5
|
import { cleanupLogs } from "./utils/logger.js";
|
|
6
|
-
import { removeWorktree } from "./utils/worktree.js";
|
|
6
|
+
import { describeWorktreeSurvival, removeWorktree } from "./utils/worktree.js";
|
|
7
7
|
import { BaseAppProps } from "./types.js";
|
|
8
8
|
|
|
9
9
|
export interface CliOptions extends BaseAppProps {
|
|
@@ -96,8 +96,14 @@ export async function startCli(options: CliOptions): Promise<void> {
|
|
|
96
96
|
if (shouldRemoveWorktree && worktreeSession) {
|
|
97
97
|
process.stdout.write("\nDeleting worktree ...\n");
|
|
98
98
|
process.chdir(worktreeSession.repoRoot);
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
// "Done." only when it is: removal is best-effort, and a surviving
|
|
100
|
+
// directory (with its branch kept for it) needs the cleanup commands
|
|
101
|
+
// printed rather than a success line over the leftovers.
|
|
102
|
+
process.stdout.write(
|
|
103
|
+
(await removeWorktree(worktreeSession))
|
|
104
|
+
? "Done.\n"
|
|
105
|
+
: `\n${describeWorktreeSurvival(worktreeSession)}\n`,
|
|
106
|
+
);
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
process.exit(0);
|
|
@@ -157,6 +157,26 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
157
157
|
</Text>
|
|
158
158
|
</Box>
|
|
159
159
|
)}
|
|
160
|
+
{/* Not dialed yet (lazy dial): the tool list comes from the local
|
|
161
|
+
snapshot of the last successful connection, and says so. */}
|
|
162
|
+
{selectedServer.status !== "connected" &&
|
|
163
|
+
selectedServer.toolsFromCache && (
|
|
164
|
+
<Box>
|
|
165
|
+
<Text>
|
|
166
|
+
<Text color="blue">Tools:</Text>{" "}
|
|
167
|
+
{selectedServer.toolsFromCache.length} tools{" "}
|
|
168
|
+
<Text color="gray" dimColor>
|
|
169
|
+
(cached
|
|
170
|
+
{selectedServer.toolsCachedAt
|
|
171
|
+
? ` · last connected ${formatTime(
|
|
172
|
+
selectedServer.toolsCachedAt,
|
|
173
|
+
)}`
|
|
174
|
+
: ""}
|
|
175
|
+
)
|
|
176
|
+
</Text>
|
|
177
|
+
</Text>
|
|
178
|
+
</Box>
|
|
179
|
+
)}
|
|
160
180
|
{selectedServer.capabilities && (
|
|
161
181
|
<Box>
|
|
162
182
|
<Text>
|
|
@@ -270,6 +290,12 @@ export const McpManager: React.FC<McpManagerProps> = ({
|
|
|
270
290
|
{server.status === "connected" && server.toolCount && (
|
|
271
291
|
<Text color="green"> · {server.toolCount} tools</Text>
|
|
272
292
|
)}
|
|
293
|
+
{server.status !== "connected" && server.toolsFromCache && (
|
|
294
|
+
<Text color="gray" dimColor>
|
|
295
|
+
{" "}
|
|
296
|
+
· {server.toolsFromCache.length} tools (cached)
|
|
297
|
+
</Text>
|
|
298
|
+
)}
|
|
273
299
|
</Text>
|
|
274
300
|
{index === state.selectedIndex && (
|
|
275
301
|
<Box marginLeft={4} flexDirection="column">
|
|
@@ -41,9 +41,21 @@ export const MessageBlockItem = ({
|
|
|
41
41
|
</Box>
|
|
42
42
|
)}
|
|
43
43
|
|
|
44
|
-
{
|
|
44
|
+
{/* A line for the human, never sent to the model (see NoticeBlock in the
|
|
45
|
+
SDK): a failed turn (`error`, red) or a hook's status line (`info`,
|
|
46
|
+
muted). */}
|
|
47
|
+
{block.type === "notice" && (
|
|
45
48
|
<Box>
|
|
46
|
-
|
|
49
|
+
{block.level === "error" ? (
|
|
50
|
+
<Text color="red">Error: {block.content}</Text>
|
|
51
|
+
) : (
|
|
52
|
+
<Text
|
|
53
|
+
color={block.level === "warning" ? "yellow" : "gray"}
|
|
54
|
+
dimColor
|
|
55
|
+
>
|
|
56
|
+
{block.content}
|
|
57
|
+
</Text>
|
|
58
|
+
)}
|
|
47
59
|
</Box>
|
|
48
60
|
)}
|
|
49
61
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RemoteControlView — `/remote-control` 的覆盖层(把这条前台会话交给手机)。
|
|
3
3
|
*
|
|
4
|
-
* 规格:docs/specs/
|
|
4
|
+
* 规格:docs/specs/remote/remote-web.md「前台 CLI 会话也能被手机接管」。
|
|
5
5
|
*
|
|
6
6
|
* **这个斜杠命令不带参数**:没有 `on` / `off` / `status` 子命令,也不收机器名。
|
|
7
7
|
* 判据是「状态自己决定动作」——`off` / `error` 时唯一合法动作是开启,`connecting`
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "../hooks/useRemoteControl.js";
|
|
15
15
|
import type {
|
|
16
16
|
Message,
|
|
17
|
+
MessageBlock,
|
|
17
18
|
McpServerStatus,
|
|
18
19
|
BackgroundTask,
|
|
19
20
|
Task,
|
|
@@ -363,6 +364,38 @@ const applyToolBlockUpdate = (
|
|
|
363
364
|
});
|
|
364
365
|
};
|
|
365
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Append a block to the last message — only when it is an assistant message
|
|
369
|
+
* (the current turn's in-flight reply). If the last message is a user message,
|
|
370
|
+
* the block belongs BELOW it: create a new assistant message instead of
|
|
371
|
+
* polluting the stale assistant from an earlier turn. Same rule the webview
|
|
372
|
+
* reducer applies (APPEND_NOTICE_BLOCK).
|
|
373
|
+
*
|
|
374
|
+
* Both the error and the system line use this: the SDK appends them to the
|
|
375
|
+
* same place (`appendBlockToLastAssistant`), so the CLI must not invent a
|
|
376
|
+
* second placement rule.
|
|
377
|
+
*/
|
|
378
|
+
const appendBlockToLastAssistant = (
|
|
379
|
+
prev: Message[],
|
|
380
|
+
block: MessageBlock,
|
|
381
|
+
): Message[] => {
|
|
382
|
+
const last = prev[prev.length - 1];
|
|
383
|
+
if (last && last.role === "assistant") {
|
|
384
|
+
return prev.map((m, idx) =>
|
|
385
|
+
idx === prev.length - 1 ? { ...m, blocks: [...m.blocks, block] } : m,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
return [
|
|
389
|
+
...prev,
|
|
390
|
+
{
|
|
391
|
+
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
392
|
+
role: "assistant",
|
|
393
|
+
timestamp: new Date().toISOString(),
|
|
394
|
+
blocks: [block],
|
|
395
|
+
},
|
|
396
|
+
];
|
|
397
|
+
};
|
|
398
|
+
|
|
366
399
|
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
367
400
|
children,
|
|
368
401
|
bypassPermissions,
|
|
@@ -726,39 +759,19 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
726
759
|
// `end` carries the authoritative final values.
|
|
727
760
|
setMessages((prev) => applyToolBlockUpdate(prev, params));
|
|
728
761
|
},
|
|
729
|
-
|
|
730
|
-
remoteStream?.
|
|
762
|
+
onNoticeAdded: ({ level, content }) => {
|
|
763
|
+
remoteStream?.onNoticeAdded({ level, content });
|
|
731
764
|
if (isExpandedRef.current) return;
|
|
732
|
-
// One-shot structural update — applied directly (
|
|
733
|
-
// delayed by the tool-running window
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
if (last && last.role === "assistant") {
|
|
743
|
-
return prev.map((m, idx) =>
|
|
744
|
-
idx === prev.length - 1
|
|
745
|
-
? {
|
|
746
|
-
...m,
|
|
747
|
-
blocks: [...m.blocks, { type: "error", content: error }],
|
|
748
|
-
}
|
|
749
|
-
: m,
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
return [
|
|
753
|
-
...prev,
|
|
754
|
-
{
|
|
755
|
-
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
756
|
-
role: "assistant",
|
|
757
|
-
timestamp: new Date().toISOString(),
|
|
758
|
-
blocks: [{ type: "error", content: error }],
|
|
759
|
-
},
|
|
760
|
-
];
|
|
761
|
-
});
|
|
765
|
+
// One-shot structural update — applied directly (a failure must not
|
|
766
|
+
// be delayed by the tool-running window; a hook's round line must
|
|
767
|
+
// not wait for the next `refreshMessages`).
|
|
768
|
+
setMessages((prev) =>
|
|
769
|
+
appendBlockToLastAssistant(prev, {
|
|
770
|
+
type: "notice",
|
|
771
|
+
level,
|
|
772
|
+
content,
|
|
773
|
+
}),
|
|
774
|
+
);
|
|
762
775
|
},
|
|
763
776
|
onLatestTotalTokensChange: (tokens) => {
|
|
764
777
|
setLatestTotalTokens(tokens);
|
|
@@ -1213,7 +1226,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1213
1226
|
timestamp: new Date().toISOString(),
|
|
1214
1227
|
blocks: [
|
|
1215
1228
|
{
|
|
1216
|
-
type: "
|
|
1229
|
+
type: "notice",
|
|
1230
|
+
level: "error",
|
|
1217
1231
|
content: `Failed to resume conversation: ${
|
|
1218
1232
|
error instanceof Error ? error.message : String(error)
|
|
1219
1233
|
}`,
|