wave-code 1.0.6 → 1.0.8

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 (54) hide show
  1. package/dist/components/AgentsManager.d.ts +7 -0
  2. package/dist/components/AgentsManager.js +109 -0
  3. package/dist/components/ChatInterface.js +1 -1
  4. package/dist/components/ConfirmationDetails.d.ts +1 -0
  5. package/dist/components/ConfirmationDetails.js +5 -3
  6. package/dist/components/ConfirmationSelector.js +17 -3
  7. package/dist/components/InputBox.js +7 -21
  8. package/dist/components/LoginCommand.js +31 -2
  9. package/dist/components/MarketplaceAddForm.js +16 -2
  10. package/dist/components/RewindCommand.js +11 -4
  11. package/dist/constants/commands.js +6 -0
  12. package/dist/contexts/useChat.d.ts +18 -2
  13. package/dist/contexts/useChat.js +114 -9
  14. package/dist/daemon/commands.d.ts +49 -0
  15. package/dist/daemon/commands.js +341 -0
  16. package/dist/daemon/jsonRpcClient.d.ts +38 -0
  17. package/dist/daemon/jsonRpcClient.js +129 -0
  18. package/dist/daemon/socketClient.d.ts +13 -0
  19. package/dist/daemon/socketClient.js +26 -0
  20. package/dist/hooks/useInputManager.d.ts +2 -0
  21. package/dist/hooks/useInputManager.js +8 -0
  22. package/dist/index.js +88 -0
  23. package/dist/managers/inputHandlers.js +3 -0
  24. package/dist/managers/inputReducer.d.ts +4 -0
  25. package/dist/managers/inputReducer.js +8 -0
  26. package/dist/reducers/agentsManagerReducer.d.ts +26 -0
  27. package/dist/reducers/agentsManagerReducer.js +54 -0
  28. package/dist/stdio/agentBridge.d.ts +15 -0
  29. package/dist/stdio/agentBridge.js +101 -20
  30. package/dist/stdio/protocol.d.ts +1 -1
  31. package/dist/utils/usageSummary.d.ts +0 -4
  32. package/dist/utils/usageSummary.js +1 -34
  33. package/package.json +2 -2
  34. package/src/components/AgentsManager.tsx +290 -0
  35. package/src/components/ChatInterface.tsx +2 -0
  36. package/src/components/ConfirmationDetails.tsx +6 -0
  37. package/src/components/ConfirmationSelector.tsx +18 -3
  38. package/src/components/InputBox.tsx +54 -45
  39. package/src/components/LoginCommand.tsx +35 -2
  40. package/src/components/MarketplaceAddForm.tsx +17 -2
  41. package/src/components/RewindCommand.tsx +10 -4
  42. package/src/constants/commands.ts +6 -0
  43. package/src/contexts/useChat.tsx +146 -7
  44. package/src/daemon/commands.ts +444 -0
  45. package/src/daemon/jsonRpcClient.ts +158 -0
  46. package/src/daemon/socketClient.ts +34 -0
  47. package/src/hooks/useInputManager.ts +8 -0
  48. package/src/index.ts +130 -0
  49. package/src/managers/inputHandlers.ts +2 -0
  50. package/src/managers/inputReducer.ts +10 -0
  51. package/src/reducers/agentsManagerReducer.ts +91 -0
  52. package/src/stdio/agentBridge.ts +123 -19
  53. package/src/stdio/protocol.ts +4 -0
  54. package/src/utils/usageSummary.ts +2 -46
@@ -0,0 +1,129 @@
1
+ /**
2
+ * JsonRpcClient — minimal JSON-RPC transport over a line-delimited duplex
3
+ * stream (one JSON object per line).
4
+ *
5
+ * Used by `wave daemon` subcommands to talk to the wave daemon's unix socket.
6
+ * Mirrors packages/desktop/src/main/stdio/jsonRpcClient.ts (packages/code
7
+ * cannot import from packages/desktop). Subclasses own the transport and hook
8
+ * in:
9
+ * - `writeLine(message)` writes one JSON line to the peer.
10
+ * - `attachReadable(readable)` wires the inbound half (socket).
11
+ * - `handleClosed(reason)` marks the transport dead and rejects every pending
12
+ * request. Idempotent — safe to call from both dispose() and an exit/close
13
+ * event on the underlying transport.
14
+ */
15
+ import { createInterface } from "readline";
16
+ export class JsonRpcClient {
17
+ constructor() {
18
+ this.nextId = 1;
19
+ this.pending = new Map();
20
+ this.handlers = new Map();
21
+ this.closedHandlers = [];
22
+ this.closed = false;
23
+ }
24
+ /** Wire an inbound Readable (socket) to the line parser. */
25
+ attachReadable(readable) {
26
+ const rl = createInterface({ input: readable });
27
+ rl.on("line", (line) => this.handleLine(line));
28
+ // readline re-emits input errors on the Interface; the transport subclass
29
+ // already handles errors on the underlying stream, swallow them here so
30
+ // they never surface as an uncaught 'error' on the Interface.
31
+ rl.on("error", () => { });
32
+ }
33
+ /** Mark the transport closed: reject every pending request. Idempotent. */
34
+ handleClosed(reason) {
35
+ if (this.closed)
36
+ return;
37
+ this.closed = true;
38
+ const error = new Error(reason);
39
+ for (const p of this.pending.values())
40
+ p.reject(error);
41
+ this.pending.clear();
42
+ for (const handler of this.closedHandlers)
43
+ handler();
44
+ this.closedHandlers = [];
45
+ }
46
+ get isClosed() {
47
+ return this.closed;
48
+ }
49
+ /** Observe transport teardown (dispose or unexpected close), fired once. */
50
+ onClosed(handler) {
51
+ this.closedHandlers.push(handler);
52
+ }
53
+ async request(method, params, sessionId) {
54
+ if (this.closed) {
55
+ throw new Error("连接已断开。wave 进程已退出,请重启编辑器或检查 CLI 安装。");
56
+ }
57
+ const id = this.nextId++;
58
+ const envelope = { id, method, params };
59
+ if (sessionId)
60
+ envelope.sessionId = sessionId;
61
+ const message = JSON.stringify(envelope) + "\n";
62
+ return new Promise((resolve, reject) => {
63
+ this.pending.set(id, { resolve, reject });
64
+ this.writeLine(message);
65
+ });
66
+ }
67
+ notify(method, params, sessionId) {
68
+ if (this.closed)
69
+ return;
70
+ const envelope = { method, params };
71
+ if (sessionId)
72
+ envelope.sessionId = sessionId;
73
+ this.writeLine(JSON.stringify(envelope) + "\n");
74
+ }
75
+ onNotification(method, handler) {
76
+ let set = this.handlers.get(method);
77
+ if (!set) {
78
+ set = new Set();
79
+ this.handlers.set(method, set);
80
+ }
81
+ set.add(handler);
82
+ }
83
+ offNotification(method, handler) {
84
+ this.handlers.get(method)?.delete(handler);
85
+ }
86
+ // ── Internal ──────────────────────────────────────────────────
87
+ handleLine(line) {
88
+ let msg;
89
+ try {
90
+ msg = JSON.parse(line);
91
+ }
92
+ catch {
93
+ console.error("[wave-jsonrpc] Failed to parse:", line);
94
+ return;
95
+ }
96
+ if (typeof msg !== "object" || msg === null)
97
+ return;
98
+ const obj = msg;
99
+ // Response (has id + result/error)
100
+ if ("id" in obj && ("result" in obj || "error" in obj)) {
101
+ const id = Number(obj.id);
102
+ const pending = this.pending.get(id);
103
+ if (pending) {
104
+ this.pending.delete(id);
105
+ if (obj.error) {
106
+ const err = obj.error;
107
+ pending.reject(new Error(err.message));
108
+ }
109
+ else {
110
+ pending.resolve(obj.result);
111
+ }
112
+ }
113
+ return;
114
+ }
115
+ // Notification (has method, no id)
116
+ if ("method" in obj && !("id" in obj)) {
117
+ const method = obj.method;
118
+ const params = obj.params;
119
+ const sessionId = typeof obj.sessionId === "string" ? obj.sessionId : undefined;
120
+ const set = this.handlers.get(method);
121
+ if (set) {
122
+ for (const handler of set) {
123
+ handler(params, sessionId);
124
+ }
125
+ }
126
+ return;
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * SocketClient — JSON-RPC transport over a unix socket connection to the wave
3
+ * daemon. Mirrors packages/desktop/src/main/stdio/socketClient.ts (packages/code
4
+ * cannot import from packages/desktop).
5
+ */
6
+ import type { Socket } from "net";
7
+ import { JsonRpcClient } from "./jsonRpcClient.js";
8
+ export declare class SocketClient extends JsonRpcClient {
9
+ private socket;
10
+ constructor(socket: Socket);
11
+ protected writeLine(message: string): void;
12
+ dispose(): void;
13
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * SocketClient — JSON-RPC transport over a unix socket connection to the wave
3
+ * daemon. Mirrors packages/desktop/src/main/stdio/socketClient.ts (packages/code
4
+ * cannot import from packages/desktop).
5
+ */
6
+ import { JsonRpcClient } from "./jsonRpcClient.js";
7
+ export class SocketClient extends JsonRpcClient {
8
+ constructor(socket) {
9
+ super();
10
+ this.socket = socket;
11
+ this.attachReadable(socket);
12
+ socket.on("close", () => {
13
+ this.handleClosed("远端连接已断开。");
14
+ });
15
+ socket.on("error", (err) => {
16
+ console.error("[wave-daemon] Socket error:", err.message);
17
+ });
18
+ }
19
+ writeLine(message) {
20
+ this.socket.write(message);
21
+ }
22
+ dispose() {
23
+ this.handleClosed("远端连接已断开。");
24
+ this.socket.destroy();
25
+ }
26
+ }
@@ -16,6 +16,7 @@ export declare const useInputManager: (callbacks?: Partial<InputManagerCallbacks
16
16
  historySearchQuery: string;
17
17
  showBackgroundTaskManager: boolean;
18
18
  showMcpManager: boolean;
19
+ showAgentsManager: boolean;
19
20
  showRewindManager: boolean;
20
21
  showHelp: boolean;
21
22
  showStatusCommand: boolean;
@@ -49,6 +50,7 @@ export declare const useInputManager: (callbacks?: Partial<InputManagerCallbacks
49
50
  processSelectorInput: (char: string) => void;
50
51
  setShowBackgroundTaskManager: (show: boolean) => void;
51
52
  setShowMcpManager: (show: boolean) => void;
53
+ setShowAgentsManager: (show: boolean) => void;
52
54
  setShowRewindManager: (show: boolean) => void;
53
55
  setShowHelp: (show: boolean) => void;
54
56
  setShowStatusCommand: (show: boolean) => void;
@@ -174,6 +174,9 @@ export const useInputManager = (callbacks = {}) => {
174
174
  else if (command === "mcp") {
175
175
  dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
176
176
  }
177
+ else if (command === "agents") {
178
+ dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: true });
179
+ }
177
180
  else if (command === "rewind") {
178
181
  dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
179
182
  }
@@ -378,6 +381,9 @@ export const useInputManager = (callbacks = {}) => {
378
381
  const setShowMcpManager = useCallback((show) => {
379
382
  dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: show });
380
383
  }, []);
384
+ const setShowAgentsManager = useCallback((show) => {
385
+ dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: show });
386
+ }, []);
381
387
  const setShowRewindManager = useCallback((show) => {
382
388
  dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: show });
383
389
  }, []);
@@ -506,6 +512,7 @@ export const useInputManager = (callbacks = {}) => {
506
512
  historySearchQuery: state.historySearchQuery,
507
513
  showBackgroundTaskManager: state.showBackgroundTaskManager,
508
514
  showMcpManager: state.showMcpManager,
515
+ showAgentsManager: state.showAgentsManager,
509
516
  showRewindManager: state.showRewindManager,
510
517
  showHelp: state.showHelp,
511
518
  showStatusCommand: state.showStatusCommand,
@@ -545,6 +552,7 @@ export const useInputManager = (callbacks = {}) => {
545
552
  // Bash/MCP Manager
546
553
  setShowBackgroundTaskManager,
547
554
  setShowMcpManager,
555
+ setShowAgentsManager,
548
556
  setShowRewindManager,
549
557
  setShowHelp,
550
558
  setShowStatusCommand,
package/dist/index.js CHANGED
@@ -178,6 +178,94 @@ export async function main() {
178
178
  const { updatePluginCommand } = await import("./commands/plugin/update.js");
179
179
  await updatePluginCommand(argv);
180
180
  });
181
+ })
182
+ .command("daemon", "Manage the wave daemon (client subcommands — to START a daemon use `wave --daemon <socket>` instead)", (yargs) => {
183
+ return yargs
184
+ .help()
185
+ .command("list", "List sessions hosted by the daemon (in-memory registry)", {}, async () => {
186
+ const { daemonListCommand, DEFAULT_DAEMON_SOCKET } = await import("./daemon/commands.js");
187
+ await daemonListCommand(DEFAULT_DAEMON_SOCKET);
188
+ })
189
+ .command("status <sessionId>", "Show a session's progress and recent messages", (yargs) => {
190
+ return yargs
191
+ .positional("sessionId", {
192
+ describe: "Session ID hosted by the daemon",
193
+ type: "string",
194
+ })
195
+ .option("lines", {
196
+ describe: "Number of recent messages to show",
197
+ default: 20,
198
+ type: "number",
199
+ });
200
+ }, async (argv) => {
201
+ const { daemonStatusCommand, DEFAULT_DAEMON_SOCKET } = await import("./daemon/commands.js");
202
+ await daemonStatusCommand(DEFAULT_DAEMON_SOCKET, argv.sessionId, argv.lines);
203
+ })
204
+ .command("send <sessionId> <message>", "Inject a message into a session and wait for the reply", (yargs) => {
205
+ return yargs
206
+ .positional("sessionId", {
207
+ describe: "Session ID hosted by the daemon",
208
+ type: "string",
209
+ })
210
+ .positional("message", {
211
+ describe: "Message to send",
212
+ type: "string",
213
+ })
214
+ .option("timeout", {
215
+ describe: "Seconds to wait for the reply (0 = no limit)",
216
+ default: 600,
217
+ type: "number",
218
+ });
219
+ }, async (argv) => {
220
+ const { daemonSendCommand, DEFAULT_DAEMON_SOCKET } = await import("./daemon/commands.js");
221
+ await daemonSendCommand(DEFAULT_DAEMON_SOCKET, argv.sessionId, argv.message, { timeout: argv.timeout });
222
+ })
223
+ .command("respond <sessionId> <requestId>", "Respond to a pending permission request", (yargs) => {
224
+ return yargs
225
+ .positional("sessionId", {
226
+ describe: "Session ID hosting the pending request",
227
+ type: "string",
228
+ })
229
+ .positional("requestId", {
230
+ describe: "Pending permission request ID",
231
+ type: "string",
232
+ })
233
+ .option("allow", {
234
+ describe: "Allow the operation",
235
+ type: "boolean",
236
+ })
237
+ .option("deny", {
238
+ describe: "Deny the operation",
239
+ type: "boolean",
240
+ })
241
+ .option("reason", {
242
+ describe: "Reason for the decision (deny)",
243
+ type: "string",
244
+ })
245
+ .option("answer", {
246
+ describe: "Answers JSON for AskUserQuestion requests",
247
+ type: "string",
248
+ })
249
+ .option("rule", {
250
+ describe: "Persist an allowed rule (e.g. Bash(ls))",
251
+ type: "string",
252
+ })
253
+ .option("mode", {
254
+ describe: "Switch the session's permission mode",
255
+ type: "string",
256
+ });
257
+ }, async (argv) => {
258
+ const { daemonRespondCommand, DEFAULT_DAEMON_SOCKET } = await import("./daemon/commands.js");
259
+ await daemonRespondCommand(DEFAULT_DAEMON_SOCKET, argv.sessionId, argv.requestId, {
260
+ allow: argv.allow,
261
+ deny: argv.deny,
262
+ reason: argv.reason,
263
+ answer: argv.answer,
264
+ rule: argv.rule,
265
+ mode: argv.mode,
266
+ });
267
+ })
268
+ .demandCommand(1, "Please specify a daemon subcommand");
181
269
  })
182
270
  .command("update", "Update WAVE Code to the latest version", {}, async () => {
183
271
  const { updateCommand } = await import("./commands/update.js");
@@ -264,6 +264,9 @@ export const handleCommandSelect = (state, dispatch, callbacks, command) => {
264
264
  else if (command === "mcp") {
265
265
  dispatch({ type: "SET_SHOW_MCP_MANAGER", payload: true });
266
266
  }
267
+ else if (command === "agents") {
268
+ dispatch({ type: "SET_SHOW_AGENTS_MANAGER", payload: true });
269
+ }
267
270
  else if (command === "rewind") {
268
271
  dispatch({ type: "SET_SHOW_REWIND_MANAGER", payload: true });
269
272
  }
@@ -111,6 +111,7 @@ export interface InputState {
111
111
  imageIdCounter: number;
112
112
  showBackgroundTaskManager: boolean;
113
113
  showMcpManager: boolean;
114
+ showAgentsManager: boolean;
114
115
  showRewindManager: boolean;
115
116
  showHelp: boolean;
116
117
  showStatusCommand: boolean;
@@ -187,6 +188,9 @@ export type InputAction = {
187
188
  } | {
188
189
  type: "SET_SHOW_MCP_MANAGER";
189
190
  payload: boolean;
191
+ } | {
192
+ type: "SET_SHOW_AGENTS_MANAGER";
193
+ payload: boolean;
190
194
  } | {
191
195
  type: "SET_SHOW_REWIND_MANAGER";
192
196
  payload: boolean;
@@ -26,6 +26,7 @@ export const initialState = {
26
26
  imageIdCounter: 1,
27
27
  showBackgroundTaskManager: false,
28
28
  showMcpManager: false,
29
+ showAgentsManager: false,
29
30
  showRewindManager: false,
30
31
  showHelp: false,
31
32
  showStatusCommand: false,
@@ -347,6 +348,12 @@ export function inputReducer(state, action) {
347
348
  showMcpManager: action.payload,
348
349
  selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
349
350
  };
351
+ case "SET_SHOW_AGENTS_MANAGER":
352
+ return {
353
+ ...state,
354
+ showAgentsManager: action.payload,
355
+ selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
356
+ };
350
357
  case "SET_SHOW_REWIND_MANAGER":
351
358
  return {
352
359
  ...state,
@@ -695,6 +702,7 @@ export function inputReducer(state, action) {
695
702
  }
696
703
  if (!(state.showBackgroundTaskManager ||
697
704
  state.showMcpManager ||
705
+ state.showAgentsManager ||
698
706
  state.showRewindManager ||
699
707
  state.showHelp ||
700
708
  state.showStatusCommand ||
@@ -0,0 +1,26 @@
1
+ import { Key } from "ink";
2
+ export type PendingEffect = {
3
+ type: "CANCEL";
4
+ };
5
+ export interface AgentsManagerState {
6
+ selectedIndex: number;
7
+ viewMode: "list" | "detail";
8
+ pendingEffect: PendingEffect | null;
9
+ }
10
+ export type AgentsManagerAction = {
11
+ type: "MOVE_UP";
12
+ } | {
13
+ type: "MOVE_DOWN";
14
+ itemCount: number;
15
+ } | {
16
+ type: "SET_VIEW_MODE";
17
+ viewMode: "list" | "detail";
18
+ } | {
19
+ type: "HANDLE_KEY";
20
+ input: string;
21
+ key: Key;
22
+ itemCount: number;
23
+ } | {
24
+ type: "CLEAR_PENDING_EFFECT";
25
+ };
26
+ export declare function agentsManagerReducer(state: AgentsManagerState, action: AgentsManagerAction): AgentsManagerState;
@@ -0,0 +1,54 @@
1
+ export function agentsManagerReducer(state, action) {
2
+ switch (action.type) {
3
+ case "MOVE_UP":
4
+ return {
5
+ ...state,
6
+ selectedIndex: Math.max(0, state.selectedIndex - 1),
7
+ };
8
+ case "MOVE_DOWN":
9
+ return {
10
+ ...state,
11
+ selectedIndex: Math.min(Math.max(0, action.itemCount - 1), state.selectedIndex + 1),
12
+ };
13
+ case "SET_VIEW_MODE":
14
+ return { ...state, viewMode: action.viewMode };
15
+ case "HANDLE_KEY": {
16
+ const { key, itemCount } = action;
17
+ if (key.return) {
18
+ if (state.viewMode === "list") {
19
+ return { ...state, viewMode: "detail" };
20
+ }
21
+ // Aligned with Claude Code AgentDetail: Enter returns to the list.
22
+ return { ...state, viewMode: "list" };
23
+ }
24
+ if (key.escape) {
25
+ if (state.viewMode === "detail") {
26
+ return { ...state, viewMode: "list" };
27
+ }
28
+ return { ...state, pendingEffect: { type: "CANCEL" } };
29
+ }
30
+ // Detail view does not respond to arrow keys (aligned with CC
31
+ // AgentDetail, which only Esc/Enter back to the list).
32
+ if (state.viewMode === "detail") {
33
+ return state;
34
+ }
35
+ if (key.upArrow) {
36
+ return {
37
+ ...state,
38
+ selectedIndex: Math.max(0, state.selectedIndex - 1),
39
+ };
40
+ }
41
+ if (key.downArrow) {
42
+ return {
43
+ ...state,
44
+ selectedIndex: Math.min(Math.max(0, itemCount - 1), state.selectedIndex + 1),
45
+ };
46
+ }
47
+ return state;
48
+ }
49
+ case "CLEAR_PENDING_EFFECT":
50
+ return { ...state, pendingEffect: null };
51
+ default:
52
+ return state;
53
+ }
54
+ }
@@ -56,7 +56,18 @@ export declare class AgentBridge {
56
56
  private removeWorktreeSession;
57
57
  private getSessionInfo;
58
58
  private updateConfig;
59
+ private getConfiguredModels;
60
+ private setModel;
59
61
  private sendMessage;
62
+ /**
63
+ * Webview hosts (desktop/vscode/jetbrains) send pasted images as inline
64
+ * data URLs — there is no local file behind them. Persist each to a temp
65
+ * file so the model gets a real path it can reference with tools (aligned
66
+ * with Claude Code's `[Image source: <path>]` metadata). Real paths pass
67
+ * through untouched; unparseable data URLs pass through as-is and are
68
+ * skipped by the SDK rather than blocking the message.
69
+ */
70
+ private persistDataUrlImages;
60
71
  private bang;
61
72
  private askBtw;
62
73
  private abortMessage;
@@ -80,6 +91,7 @@ export declare class AgentBridge {
80
91
  private connectMcpServer;
81
92
  private disconnectMcpServer;
82
93
  private getSlashCommands;
94
+ private getSubagentConfigurations;
83
95
  private searchFiles;
84
96
  /**
85
97
  * Writes an uploaded file (from the desktop/webview "+上传文件" flow) into the
@@ -96,6 +108,9 @@ export declare class AgentBridge {
96
108
  * client disconnected (daemon mode). Responding to any listed requestId
97
109
  * resolves the in-process promise. */
98
110
  private listPendingPermissions;
111
+ /** Daemon list: expose the in-memory session registry (live sessions only,
112
+ * not disk-scanning). Registration order is preserved. */
113
+ private listDaemonSessions;
99
114
  private getAuthStatus;
100
115
  private login;
101
116
  private logout;