wave-code 1.0.10 → 1.1.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.
Files changed (248) hide show
  1. package/bin/wave-code.js +8 -2
  2. package/dist/bundle/wave.mjs +3231 -0
  3. package/package.json +24 -25
  4. package/src/cli.tsx +6 -1
  5. package/src/commands/update.ts +2 -7
  6. package/src/components/App.tsx +3 -7
  7. package/src/components/BtwDisplay.tsx +10 -1
  8. package/src/components/ChatInterface.tsx +21 -52
  9. package/src/components/ConfirmationDetails.tsx +144 -37
  10. package/src/components/DiffDisplay.tsx +335 -301
  11. package/src/components/InputBox.tsx +17 -2
  12. package/src/components/LoadingIndicator.tsx +16 -2
  13. package/src/components/MessageBlockItem.tsx +2 -4
  14. package/src/components/MessageList.tsx +75 -14
  15. package/src/components/ReasoningDisplay.tsx +2 -4
  16. package/src/components/SessionSelector.tsx +82 -11
  17. package/src/components/SkillsManager.tsx +312 -0
  18. package/src/components/StatusCommand.tsx +2 -9
  19. package/src/components/ToolDisplay.tsx +2 -4
  20. package/src/constants/commands.ts +6 -0
  21. package/src/contexts/useChat.tsx +245 -301
  22. package/src/daemon/commands.ts +57 -27
  23. package/src/daemon/jsonRpcClient.ts +1 -1
  24. package/src/daemon/socketClient.ts +2 -2
  25. package/src/hooks/useInputManager.ts +7 -1
  26. package/src/index.ts +57 -10
  27. package/src/managers/inputHandlers.ts +2 -0
  28. package/src/managers/inputReducer.ts +195 -104
  29. package/src/print-cli.ts +25 -17
  30. package/src/reducers/selectorReducer.ts +10 -0
  31. package/src/reducers/skillsManagerReducer.ts +91 -0
  32. package/src/session-selector-cli.tsx +269 -28
  33. package/src/stdio/agentBridge.ts +30 -21
  34. package/src/stdio/daemonServer.ts +3 -1
  35. package/src/stdio/protocol.ts +2 -0
  36. package/src/utils/clipboard.ts +21 -0
  37. package/src/utils/highlightUtils.ts +46 -1
  38. package/src/utils/readPackageJson.ts +29 -0
  39. package/src/utils/streamingText.ts +11 -0
  40. package/src/utils/worktree.ts +191 -43
  41. package/dist/cli.d.ts +0 -6
  42. package/dist/cli.js +0 -53
  43. package/dist/commands/plugin/disable.d.ts +0 -5
  44. package/dist/commands/plugin/disable.js +0 -15
  45. package/dist/commands/plugin/enable.d.ts +0 -5
  46. package/dist/commands/plugin/enable.js +0 -15
  47. package/dist/commands/plugin/install.d.ts +0 -5
  48. package/dist/commands/plugin/install.js +0 -21
  49. package/dist/commands/plugin/list.d.ts +0 -1
  50. package/dist/commands/plugin/list.js +0 -31
  51. package/dist/commands/plugin/marketplace.d.ts +0 -10
  52. package/dist/commands/plugin/marketplace.js +0 -90
  53. package/dist/commands/plugin/uninstall.d.ts +0 -3
  54. package/dist/commands/plugin/uninstall.js +0 -16
  55. package/dist/commands/plugin/update.d.ts +0 -3
  56. package/dist/commands/plugin/update.js +0 -15
  57. package/dist/commands/update.d.ts +0 -1
  58. package/dist/commands/update.js +0 -97
  59. package/dist/components/AgentsManager.d.ts +0 -7
  60. package/dist/components/AgentsManager.js +0 -109
  61. package/dist/components/App.d.ts +0 -9
  62. package/dist/components/App.js +0 -76
  63. package/dist/components/BackgroundTaskManager.d.ts +0 -5
  64. package/dist/components/BackgroundTaskManager.js +0 -99
  65. package/dist/components/BangDisplay.d.ts +0 -8
  66. package/dist/components/BangDisplay.js +0 -17
  67. package/dist/components/BtwDisplay.d.ts +0 -7
  68. package/dist/components/BtwDisplay.js +0 -19
  69. package/dist/components/ChatInterface.d.ts +0 -2
  70. package/dist/components/ChatInterface.js +0 -61
  71. package/dist/components/CommandSelector.d.ts +0 -10
  72. package/dist/components/CommandSelector.js +0 -66
  73. package/dist/components/CompactDisplay.d.ts +0 -8
  74. package/dist/components/CompactDisplay.js +0 -13
  75. package/dist/components/ConfirmationDetails.d.ts +0 -9
  76. package/dist/components/ConfirmationDetails.js +0 -45
  77. package/dist/components/ConfirmationSelector.d.ts +0 -13
  78. package/dist/components/ConfirmationSelector.js +0 -127
  79. package/dist/components/DiffDisplay.d.ts +0 -8
  80. package/dist/components/DiffDisplay.js +0 -201
  81. package/dist/components/DiscoverView.d.ts +0 -2
  82. package/dist/components/DiscoverView.js +0 -37
  83. package/dist/components/FileSelector.d.ts +0 -11
  84. package/dist/components/FileSelector.js +0 -50
  85. package/dist/components/HelpView.d.ts +0 -7
  86. package/dist/components/HelpView.js +0 -74
  87. package/dist/components/HistorySearch.d.ts +0 -8
  88. package/dist/components/HistorySearch.js +0 -66
  89. package/dist/components/InputBox.d.ts +0 -24
  90. package/dist/components/InputBox.js +0 -149
  91. package/dist/components/InstalledView.d.ts +0 -2
  92. package/dist/components/InstalledView.js +0 -45
  93. package/dist/components/LoadingIndicator.d.ts +0 -10
  94. package/dist/components/LoadingIndicator.js +0 -6
  95. package/dist/components/LoginCommand.d.ts +0 -5
  96. package/dist/components/LoginCommand.js +0 -153
  97. package/dist/components/Markdown.d.ts +0 -5
  98. package/dist/components/Markdown.js +0 -127
  99. package/dist/components/MarketplaceAddForm.d.ts +0 -2
  100. package/dist/components/MarketplaceAddForm.js +0 -55
  101. package/dist/components/MarketplaceDetail.d.ts +0 -2
  102. package/dist/components/MarketplaceDetail.js +0 -74
  103. package/dist/components/MarketplaceList.d.ts +0 -8
  104. package/dist/components/MarketplaceList.js +0 -17
  105. package/dist/components/MarketplaceView.d.ts +0 -2
  106. package/dist/components/MarketplaceView.js +0 -41
  107. package/dist/components/McpManager.d.ts +0 -9
  108. package/dist/components/McpManager.js +0 -88
  109. package/dist/components/MessageBlockItem.d.ts +0 -8
  110. package/dist/components/MessageBlockItem.js +0 -15
  111. package/dist/components/MessageList.d.ts +0 -10
  112. package/dist/components/MessageList.js +0 -80
  113. package/dist/components/ModelSelector.d.ts +0 -8
  114. package/dist/components/ModelSelector.js +0 -45
  115. package/dist/components/Notifications.d.ts +0 -7
  116. package/dist/components/Notifications.js +0 -9
  117. package/dist/components/PlanDisplay.d.ts +0 -7
  118. package/dist/components/PlanDisplay.js +0 -6
  119. package/dist/components/PluginDetail.d.ts +0 -2
  120. package/dist/components/PluginDetail.js +0 -87
  121. package/dist/components/PluginList.d.ts +0 -13
  122. package/dist/components/PluginList.js +0 -15
  123. package/dist/components/PluginManagerShell.d.ts +0 -6
  124. package/dist/components/PluginManagerShell.js +0 -92
  125. package/dist/components/PluginManagerTypes.d.ts +0 -35
  126. package/dist/components/PluginManagerTypes.js +0 -1
  127. package/dist/components/QueuedMessageList.d.ts +0 -2
  128. package/dist/components/QueuedMessageList.js +0 -18
  129. package/dist/components/ReasoningDisplay.d.ts +0 -8
  130. package/dist/components/ReasoningDisplay.js +0 -13
  131. package/dist/components/RewindCommand.d.ts +0 -12
  132. package/dist/components/RewindCommand.js +0 -78
  133. package/dist/components/SessionSelector.d.ts +0 -12
  134. package/dist/components/SessionSelector.js +0 -47
  135. package/dist/components/StatusCommand.d.ts +0 -5
  136. package/dist/components/StatusCommand.js +0 -28
  137. package/dist/components/StatusLine.d.ts +0 -6
  138. package/dist/components/StatusLine.js +0 -9
  139. package/dist/components/TaskList.d.ts +0 -5
  140. package/dist/components/TaskList.js +0 -197
  141. package/dist/components/TaskNotificationMessage.d.ts +0 -5
  142. package/dist/components/TaskNotificationMessage.js +0 -12
  143. package/dist/components/ToolDisplay.d.ts +0 -9
  144. package/dist/components/ToolDisplay.js +0 -55
  145. package/dist/components/WorkflowManager.d.ts +0 -5
  146. package/dist/components/WorkflowManager.js +0 -102
  147. package/dist/components/WorktreeExitPrompt.d.ts +0 -12
  148. package/dist/components/WorktreeExitPrompt.js +0 -35
  149. package/dist/constants/commands.d.ts +0 -2
  150. package/dist/constants/commands.js +0 -92
  151. package/dist/contexts/PluginManagerContext.d.ts +0 -3
  152. package/dist/contexts/PluginManagerContext.js +0 -9
  153. package/dist/contexts/useAppConfig.d.ts +0 -10
  154. package/dist/contexts/useAppConfig.js +0 -13
  155. package/dist/contexts/useChat.d.ts +0 -101
  156. package/dist/contexts/useChat.js +0 -993
  157. package/dist/contracts/status.d.ts +0 -7
  158. package/dist/contracts/status.js +0 -1
  159. package/dist/daemon/commands.d.ts +0 -49
  160. package/dist/daemon/commands.js +0 -341
  161. package/dist/daemon/jsonRpcClient.d.ts +0 -38
  162. package/dist/daemon/jsonRpcClient.js +0 -129
  163. package/dist/daemon/socketClient.d.ts +0 -13
  164. package/dist/daemon/socketClient.js +0 -26
  165. package/dist/daemon-cli.d.ts +0 -10
  166. package/dist/daemon-cli.js +0 -15
  167. package/dist/hooks/useInputManager.d.ts +0 -75
  168. package/dist/hooks/useInputManager.js +0 -583
  169. package/dist/hooks/usePluginManager.d.ts +0 -4
  170. package/dist/hooks/usePluginManager.js +0 -265
  171. package/dist/hooks/useTasks.d.ts +0 -1
  172. package/dist/hooks/useTasks.js +0 -5
  173. package/dist/index.d.ts +0 -4
  174. package/dist/index.js +0 -447
  175. package/dist/managers/inputHandlers.d.ts +0 -30
  176. package/dist/managers/inputHandlers.js +0 -625
  177. package/dist/managers/inputReducer.d.ts +0 -271
  178. package/dist/managers/inputReducer.js +0 -1121
  179. package/dist/plugin-manager-cli.d.ts +0 -5
  180. package/dist/plugin-manager-cli.js +0 -12
  181. package/dist/print-cli.d.ts +0 -10
  182. package/dist/print-cli.js +0 -209
  183. package/dist/reducers/agentsManagerReducer.d.ts +0 -26
  184. package/dist/reducers/agentsManagerReducer.js +0 -54
  185. package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
  186. package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
  187. package/dist/reducers/confirmationReducer.d.ts +0 -37
  188. package/dist/reducers/confirmationReducer.js +0 -193
  189. package/dist/reducers/helpSelectorReducer.d.ts +0 -25
  190. package/dist/reducers/helpSelectorReducer.js +0 -52
  191. package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
  192. package/dist/reducers/marketplaceAddFormReducer.js +0 -58
  193. package/dist/reducers/mcpManagerReducer.d.ts +0 -37
  194. package/dist/reducers/mcpManagerReducer.js +0 -74
  195. package/dist/reducers/pluginDetailReducer.d.ts +0 -32
  196. package/dist/reducers/pluginDetailReducer.js +0 -70
  197. package/dist/reducers/questionReducer.d.ts +0 -82
  198. package/dist/reducers/questionReducer.js +0 -375
  199. package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
  200. package/dist/reducers/rewindSelectorReducer.js +0 -37
  201. package/dist/reducers/selectorReducer.d.ts +0 -23
  202. package/dist/reducers/selectorReducer.js +0 -42
  203. package/dist/reducers/workflowManagerReducer.d.ts +0 -40
  204. package/dist/reducers/workflowManagerReducer.js +0 -100
  205. package/dist/session-selector-cli.d.ts +0 -3
  206. package/dist/session-selector-cli.js +0 -25
  207. package/dist/stdio/agentBridge.d.ts +0 -139
  208. package/dist/stdio/agentBridge.js +0 -999
  209. package/dist/stdio/daemonServer.d.ts +0 -67
  210. package/dist/stdio/daemonServer.js +0 -191
  211. package/dist/stdio/index.d.ts +0 -5
  212. package/dist/stdio/index.js +0 -5
  213. package/dist/stdio/jsonRpcConnection.d.ts +0 -39
  214. package/dist/stdio/jsonRpcConnection.js +0 -135
  215. package/dist/stdio/protocol.d.ts +0 -41
  216. package/dist/stdio/protocol.js +0 -26
  217. package/dist/stdio/stdioServer.d.ts +0 -36
  218. package/dist/stdio/stdioServer.js +0 -51
  219. package/dist/stdio-cli.d.ts +0 -8
  220. package/dist/stdio-cli.js +0 -15
  221. package/dist/types.d.ts +0 -20
  222. package/dist/types.js +0 -1
  223. package/dist/utils/bracketedPaste.d.ts +0 -39
  224. package/dist/utils/bracketedPaste.js +0 -122
  225. package/dist/utils/clipboard.d.ts +0 -21
  226. package/dist/utils/clipboard.js +0 -347
  227. package/dist/utils/constants.d.ts +0 -16
  228. package/dist/utils/constants.js +0 -18
  229. package/dist/utils/highlightUtils.d.ts +0 -1
  230. package/dist/utils/highlightUtils.js +0 -93
  231. package/dist/utils/inputUtils.d.ts +0 -17
  232. package/dist/utils/inputUtils.js +0 -67
  233. package/dist/utils/logger.d.ts +0 -71
  234. package/dist/utils/logger.js +0 -259
  235. package/dist/utils/markdownTable.d.ts +0 -34
  236. package/dist/utils/markdownTable.js +0 -302
  237. package/dist/utils/rewindCheckpoints.d.ts +0 -8
  238. package/dist/utils/rewindCheckpoints.js +0 -17
  239. package/dist/utils/throttle.d.ts +0 -14
  240. package/dist/utils/throttle.js +0 -55
  241. package/dist/utils/toolParameterTransforms.d.ts +0 -18
  242. package/dist/utils/toolParameterTransforms.js +0 -73
  243. package/dist/utils/usageSummary.d.ts +0 -28
  244. package/dist/utils/usageSummary.js +0 -121
  245. package/dist/utils/version.d.ts +0 -1
  246. package/dist/utils/version.js +0 -4
  247. package/dist/utils/worktree.d.ts +0 -35
  248. package/dist/utils/worktree.js +0 -219
@@ -1,7 +0,0 @@
1
- export interface AgentStatus {
2
- version: string;
3
- sessionId: string;
4
- cwd: string;
5
- baseURL: string;
6
- model: string;
7
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,49 +0,0 @@
1
- /**
2
- * `wave daemon` client subcommands — talk to the wave daemon's unix socket
3
- * (JSON-RPC over newline-delimited JSON) to list hosted sessions, inspect
4
- * progress, inject messages and respond to pending permission requests.
5
- *
6
- * All subcommands are non-interactive: results go to stdout, diagnostics to
7
- * stderr, and every handler calls process.exit() itself (yargs would fall
8
- * through to the TUI otherwise). Every command connects to the fixed default
9
- * socket `~/.wave/daemon.sock` — the daemon only runs on remote hosts, so no
10
- * `--socket` override is offered (spec: daemon-command.md).
11
- *
12
- * Attach semantics: `initialize {workdir, restoreSessionId}` + `restoreSession`
13
- * re-attach to a live session in the daemon's in-memory registry, or reload a
14
- * transcript from disk under the current working directory. A session that is
15
- * nowhere (live registry or disk) silently starts a FRESH session under a
16
- * different id — the only reliable existence check is the `restoreSession`
17
- * rejection ("Session not found: <id>"), after which the junk fresh session
18
- * must be destroyed via the envelope sessionId returned by `initialize`.
19
- */
20
- /** Fixed default daemon socket (spec: 默认 socket 固定,无 --socket 覆盖). */
21
- export declare const DEFAULT_DAEMON_SOCKET: string;
22
- export declare function daemonListCommand(socketPath: string): Promise<void>;
23
- export declare function daemonStatusCommand(socketPath: string, sessionId: string, lines?: number): Promise<void>;
24
- export interface SendOptions {
25
- timeout: number;
26
- }
27
- /**
28
- * Send a message and wait for the reply that corresponds to it.
29
- *
30
- * Completion detection: `sendMessage` on an idle session resolves only after
31
- * the whole turn finishes (InteractionService awaits sendAIMessage), while on a
32
- * busy session it enqueues and returns immediately — so stopping on a bare
33
- * `loadingChange:false` would exit early on the PREVIOUS turn's completion when
34
- * queued behind a busy session. Instead, track the message IDs: `ourUserMessage`
35
- * is the user message added when OUR turn starts (userMessageAdded), and the
36
- * reply is the last assistantMessageAdded observed after it. A stale
37
- * loading:false can then never satisfy the wait condition early (the reply has
38
- * not been added yet).
39
- */
40
- export declare function daemonSendCommand(socketPath: string, sessionId: string, message: string, options?: SendOptions): Promise<void>;
41
- export interface RespondOptions {
42
- allow?: boolean;
43
- deny?: boolean;
44
- reason?: string;
45
- answer?: string;
46
- rule?: string;
47
- mode?: string;
48
- }
49
- export declare function daemonRespondCommand(socketPath: string, sessionId: string, requestId: string, options: RespondOptions): Promise<void>;
@@ -1,341 +0,0 @@
1
- /**
2
- * `wave daemon` client subcommands — talk to the wave daemon's unix socket
3
- * (JSON-RPC over newline-delimited JSON) to list hosted sessions, inspect
4
- * progress, inject messages and respond to pending permission requests.
5
- *
6
- * All subcommands are non-interactive: results go to stdout, diagnostics to
7
- * stderr, and every handler calls process.exit() itself (yargs would fall
8
- * through to the TUI otherwise). Every command connects to the fixed default
9
- * socket `~/.wave/daemon.sock` — the daemon only runs on remote hosts, so no
10
- * `--socket` override is offered (spec: daemon-command.md).
11
- *
12
- * Attach semantics: `initialize {workdir, restoreSessionId}` + `restoreSession`
13
- * re-attach to a live session in the daemon's in-memory registry, or reload a
14
- * transcript from disk under the current working directory. A session that is
15
- * nowhere (live registry or disk) silently starts a FRESH session under a
16
- * different id — the only reliable existence check is the `restoreSession`
17
- * rejection ("Session not found: <id>"), after which the junk fresh session
18
- * must be destroyed via the envelope sessionId returned by `initialize`.
19
- */
20
- import net from "node:net";
21
- import os from "node:os";
22
- import path from "node:path";
23
- import { ASK_USER_QUESTION_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, getMessageContent, } from "wave-agent-sdk";
24
- import { SocketClient } from "./socketClient.js";
25
- /** Fixed default daemon socket (spec: 默认 socket 固定,无 --socket 覆盖). */
26
- export const DEFAULT_DAEMON_SOCKET = path.join(os.homedir(), ".wave", "daemon.sock");
27
- const PERMISSION_MODES = [
28
- "default",
29
- "bypassPermissions",
30
- "acceptEdits",
31
- "plan",
32
- "dontAsk",
33
- ];
34
- // ── Connection helpers ─────────────────────────────────────────
35
- function connectDaemon(socketPath) {
36
- return new Promise((resolve, reject) => {
37
- const socket = net.createConnection(socketPath);
38
- socket.once("connect", () => resolve(new SocketClient(socket)));
39
- socket.once("error", (err) => {
40
- socket.destroy();
41
- reject(err);
42
- });
43
- });
44
- }
45
- /** Connect or fail fast with the spec'd error; daemon idle-exits after 60s. */
46
- async function connectDaemonOrExit(socketPath) {
47
- try {
48
- return await connectDaemon(socketPath);
49
- }
50
- catch (err) {
51
- const code = err.code;
52
- console.error(`无法连接 daemon socket ${socketPath}:daemon 未运行?(daemon 空闲 60 秒自动退出)` +
53
- (code ? ` (${code})` : ""));
54
- process.exit(1);
55
- }
56
- }
57
- function fail(message) {
58
- console.error(message);
59
- process.exit(1);
60
- }
61
- /**
62
- * Attach to a session; returns the initialized sessionId + working directory.
63
- * Exits (nonzero) with the spec'd error when the session exists neither in the
64
- * daemon registry nor on disk, destroying the fresh session that `initialize`
65
- * silently created.
66
- */
67
- async function attachSession(client, sessionId) {
68
- const init = (await client.request("initialize", {
69
- workdir: process.cwd(),
70
- restoreSessionId: sessionId,
71
- }));
72
- const initId = init.sessionId;
73
- try {
74
- await client.request("restoreSession", { sessionId }, initId);
75
- }
76
- catch (err) {
77
- if (err.message.includes("Session not found")) {
78
- // initialize silently started a junk fresh session — remove it from the
79
- // registry so the failed attach leaves no trace (spec: 会话不存在错误).
80
- await client.request("destroy", undefined, initId).catch(() => { });
81
- fail(`会话不存在或未被该 daemon 托管:${sessionId}`);
82
- }
83
- throw err;
84
- }
85
- return init;
86
- }
87
- async function listPendingPermissions(client) {
88
- const result = (await client.request("listPendingPermissions"));
89
- return result.requests ?? [];
90
- }
91
- function sleep(ms) {
92
- return new Promise((resolve) => setTimeout(resolve, ms));
93
- }
94
- // ── list ───────────────────────────────────────────────────────
95
- export async function daemonListCommand(socketPath) {
96
- let client;
97
- try {
98
- client = await connectDaemonOrExit(socketPath);
99
- const result = (await client.request("listDaemonSessions"));
100
- const sessions = result.sessions ?? [];
101
- if (sessions.length > 0) {
102
- const rows = sessions.map((s) => ({
103
- sessionId: s.sessionId,
104
- status: s.isLoading ? "生成中" : "空闲",
105
- messageCount: String(s.messageCount),
106
- workingDirectory: s.workingDirectory,
107
- }));
108
- const width = (key) => Math.max(...rows.map((r) => r[key].length), key.length);
109
- const pad = (value, w) => value.padEnd(w);
110
- console.log(`${pad("会话", width("sessionId"))} ${pad("状态", width("status"))} ${pad("消息数", width("messageCount"))} 工作目录`);
111
- for (const r of rows) {
112
- console.log(`${pad(r.sessionId, width("sessionId"))} ${pad(r.status, width("status"))} ${pad(r.messageCount, width("messageCount"))} ${r.workingDirectory}`);
113
- }
114
- }
115
- else {
116
- // Daemon idle-exit is normal — an empty registry is not an error.
117
- console.log("无会话");
118
- }
119
- }
120
- catch (err) {
121
- fail(`wave daemon list 失败:${err.message}`);
122
- }
123
- finally {
124
- await client?.dispose();
125
- }
126
- // Exits outside the try so the success path's exit is never re-wrapped by the
127
- // error handler above.
128
- process.exit(0);
129
- }
130
- // ── status ─────────────────────────────────────────────────────
131
- function summarizeToolInput(context) {
132
- const input = context.toolInput;
133
- if (!input || Object.keys(input).length === 0)
134
- return "";
135
- let text;
136
- try {
137
- text = JSON.stringify(input);
138
- }
139
- catch {
140
- text = "";
141
- }
142
- return text.length > 80 ? `${text.slice(0, 80)}…` : text;
143
- }
144
- export async function daemonStatusCommand(socketPath, sessionId, lines = 20) {
145
- let client;
146
- try {
147
- client = await connectDaemonOrExit(socketPath);
148
- // Subscribe BEFORE initialize/restoreSession so the replayed loadingChange
149
- // snapshot is captured (spec: 依据重放的 loadingChange 快照显示状态).
150
- let loading = false;
151
- client.onNotification("loadingChange", (params) => {
152
- loading = params.loading;
153
- });
154
- const init = await attachSession(client, sessionId);
155
- const initId = init.sessionId;
156
- // listPendingPermissions is the authoritative "waiting for approval" signal
157
- // (spec: 单凭消息无法区分等审批与执行中,须结合 listPendingPermissions).
158
- const pending = (await listPendingPermissions(client)).filter((r) => r.sessionId === initId || r.sessionId === sessionId);
159
- const messages = (await client.request("getMessages", undefined, initId));
160
- const status = pending.length > 0 ? "等待审批" : loading ? "生成中" : "空闲";
161
- console.log(`会话: ${initId}`);
162
- console.log(`工作目录: ${init.workingDirectory}`);
163
- console.log(`状态: ${status}`);
164
- if (pending.length > 0) {
165
- console.log("");
166
- console.log("待审批请求:");
167
- for (const r of pending) {
168
- const params = summarizeToolInput(r.context);
169
- console.log(` ${r.requestId} ${r.context.toolName}${params ? ` ${params}` : ""}`);
170
- }
171
- }
172
- const recent = messages.messages.slice(-lines);
173
- if (recent.length > 0) {
174
- console.log("");
175
- console.log(`最近消息 (${recent.length}):`);
176
- for (const m of recent) {
177
- const text = getMessageContent(m).replace(/\s+/g, " ").trim();
178
- if (!text)
179
- continue; // tool-only messages carry no readable text
180
- console.log(` [${m.role}] ${text}`);
181
- }
182
- }
183
- }
184
- catch (err) {
185
- fail(`wave daemon status 失败:${err.message}`);
186
- }
187
- finally {
188
- await client?.dispose();
189
- }
190
- process.exit(0);
191
- }
192
- /**
193
- * Send a message and wait for the reply that corresponds to it.
194
- *
195
- * Completion detection: `sendMessage` on an idle session resolves only after
196
- * the whole turn finishes (InteractionService awaits sendAIMessage), while on a
197
- * busy session it enqueues and returns immediately — so stopping on a bare
198
- * `loadingChange:false` would exit early on the PREVIOUS turn's completion when
199
- * queued behind a busy session. Instead, track the message IDs: `ourUserMessage`
200
- * is the user message added when OUR turn starts (userMessageAdded), and the
201
- * reply is the last assistantMessageAdded observed after it. A stale
202
- * loading:false can then never satisfy the wait condition early (the reply has
203
- * not been added yet).
204
- */
205
- export async function daemonSendCommand(socketPath, sessionId, message, options = { timeout: 600 }) {
206
- // connectDaemonOrExit exits on failure — no client to dispose in that case.
207
- const client = await connectDaemonOrExit(socketPath);
208
- let loading = false;
209
- let sent = false;
210
- let ourUserMessageId;
211
- let replyMessageId;
212
- client.onNotification("userMessageAdded", (params) => {
213
- if (!sent)
214
- return; // ignore messages added during attach
215
- ourUserMessageId = params.message.id;
216
- });
217
- client.onNotification("assistantMessageAdded", (params) => {
218
- if (!sent || ourUserMessageId === undefined)
219
- return; // not our turn yet
220
- replyMessageId = params.message.id;
221
- });
222
- client.onNotification("loadingChange", (params) => {
223
- loading = params.loading;
224
- });
225
- let initId;
226
- try {
227
- initId = (await attachSession(client, sessionId)).sessionId;
228
- sent = true;
229
- await client.request("sendMessage", { text: message }, initId);
230
- }
231
- catch (err) {
232
- client.dispose();
233
- fail(`wave daemon send 失败:${err.message}`);
234
- }
235
- // Wait for the reply that corresponds to our message.
236
- const started = Date.now();
237
- const timeoutMs = options.timeout === 0 ? Infinity : options.timeout * 1000;
238
- while (!(loading === false && replyMessageId !== undefined)) {
239
- if (Date.now() - started > timeoutMs) {
240
- // Timeout backstop: the most likely cause is a session waiting on a
241
- // permission approval — point the user at respond (spec: 不无限期挂起).
242
- const pending = (await listPendingPermissions(client)).filter((r) => r.sessionId === sessionId || r.sessionId === initId);
243
- client.dispose();
244
- if (pending.length > 0) {
245
- fail(`会话等待权限审批,请通过 \`wave daemon respond ${sessionId} ${pending[0].requestId}\` 处理后重试`);
246
- }
247
- fail(options.timeout === 0
248
- ? "等待回复超时"
249
- : `等待回复超时(${options.timeout} 秒),未收到助手回复`);
250
- }
251
- await sleep(200);
252
- }
253
- try {
254
- const result = (await client.request("getMessages", undefined, initId));
255
- const reply = result.messages.find((m) => m.id === replyMessageId);
256
- // Pure final-reply text only; streaming deltas / subagent internals never
257
- // reach stdout (spec: send 输出纯净性).
258
- if (reply) {
259
- const content = getMessageContent(reply).replace(/\s+/g, " ").trim();
260
- if (content)
261
- console.log(content);
262
- }
263
- }
264
- catch (err) {
265
- fail(`wave daemon send 失败:${err.message}`);
266
- }
267
- finally {
268
- client.dispose();
269
- }
270
- process.exit(0);
271
- }
272
- export async function daemonRespondCommand(socketPath, sessionId, requestId, options) {
273
- if (!!options.allow === !!options.deny) {
274
- fail("请指定 --allow 或 --deny(二选一)");
275
- }
276
- let client;
277
- try {
278
- client = await connectDaemonOrExit(socketPath);
279
- // The server silently ignores permissionResponse for unknown requestIds —
280
- // validate first so the user is never misled into thinking approval landed.
281
- const pending = await listPendingPermissions(client);
282
- const req = pending.find((r) => r.requestId === requestId);
283
- if (!req) {
284
- fail("该请求不存在或已处理");
285
- }
286
- if (req.sessionId && req.sessionId !== sessionId) {
287
- // Cross-check before notifying; never touch another session's request.
288
- fail("会话不存在或未被该 daemon 托管");
289
- }
290
- let decision;
291
- if (options.deny) {
292
- decision = { behavior: "deny", message: options.reason };
293
- }
294
- else {
295
- // Per-tool auto-completion, mirroring the desktop ConfirmationDialog
296
- // semantics (spec: 决策并非单一 allow/deny,须按工具智能补全).
297
- const toolName = req.context.toolName;
298
- if (toolName === ENTER_PLAN_MODE_TOOL_NAME) {
299
- decision = { behavior: "allow", newPermissionMode: "plan" };
300
- }
301
- else if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
302
- decision = { behavior: "allow", newPermissionMode: "default" };
303
- }
304
- else if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
305
- if (!options.answer) {
306
- fail("AskUserQuestion 请求需要 --answer 提供答案 JSON");
307
- }
308
- let answers;
309
- try {
310
- answers = JSON.parse(options.answer);
311
- }
312
- catch {
313
- fail("--answer 不是合法的 JSON");
314
- }
315
- decision = { behavior: "allow", message: JSON.stringify(answers) };
316
- }
317
- else {
318
- decision = { behavior: "allow" };
319
- }
320
- if (options.rule)
321
- decision.newPermissionRule = options.rule;
322
- if (options.mode) {
323
- if (!PERMISSION_MODES.includes(options.mode)) {
324
- fail(`无效的权限模式:${options.mode}(可选:${PERMISSION_MODES.join("、")})`);
325
- }
326
- decision.newPermissionMode = options.mode;
327
- }
328
- }
329
- // Mirror desktop stdioAgent.sendPermissionResponse: envelope sessionId
330
- // present, decision built from the pending request's tool.
331
- client.notify("permissionResponse", { requestId, decision }, sessionId);
332
- console.log(`已处理审批请求:${requestId}`);
333
- }
334
- catch (err) {
335
- fail(`wave daemon respond 失败:${err.message}`);
336
- }
337
- finally {
338
- await client?.dispose();
339
- }
340
- process.exit(0);
341
- }
@@ -1,38 +0,0 @@
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 type { Readable } from "stream";
16
- export type NotificationHandler = (params: unknown, sessionId?: string) => void;
17
- export declare abstract class JsonRpcClient {
18
- private nextId;
19
- private pending;
20
- private handlers;
21
- private closedHandlers;
22
- private closed;
23
- protected abstract writeLine(message: string): void;
24
- /** Wire an inbound Readable (socket) to the line parser. */
25
- protected attachReadable(readable: Readable): void;
26
- /** Mark the transport closed: reject every pending request. Idempotent. */
27
- protected handleClosed(reason: string): void;
28
- protected get isClosed(): boolean;
29
- /** Close the transport and reject pending requests (subclass tears down its stream). */
30
- abstract dispose(): void;
31
- /** Observe transport teardown (dispose or unexpected close), fired once. */
32
- onClosed(handler: () => void): void;
33
- request(method: string, params?: unknown, sessionId?: string): Promise<unknown>;
34
- notify(method: string, params?: unknown, sessionId?: string): void;
35
- onNotification(method: string, handler: NotificationHandler): void;
36
- offNotification(method: string, handler: NotificationHandler): void;
37
- private handleLine;
38
- }
@@ -1,129 +0,0 @@
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
- }
@@ -1,13 +0,0 @@
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
- }
@@ -1,26 +0,0 @@
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
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * daemon-cli.ts — Entry point for `wave --daemon <socket-path>` mode.
3
- *
4
- * Starts a DaemonServer that serves JSON-RPC over a unix socket. The desktop
5
- * app launches this on a remote host via nohup/setsid and tunnels the socket
6
- * back with `ssh -L`; multiple attach/detach cycles share one process, so
7
- * sessions and pending permissions survive client disconnects. The net server
8
- * keeps the process alive — there is no stdin to wait on.
9
- */
10
- export declare function startDaemonCli(socketPath: string): Promise<void>;
@@ -1,15 +0,0 @@
1
- /**
2
- * daemon-cli.ts — Entry point for `wave --daemon <socket-path>` mode.
3
- *
4
- * Starts a DaemonServer that serves JSON-RPC over a unix socket. The desktop
5
- * app launches this on a remote host via nohup/setsid and tunnels the socket
6
- * back with `ssh -L`; multiple attach/detach cycles share one process, so
7
- * sessions and pending permissions survive client disconnects. The net server
8
- * keeps the process alive — there is no stdin to wait on.
9
- */
10
- import { DaemonServer } from "./stdio/daemonServer.js";
11
- export async function startDaemonCli(socketPath) {
12
- const server = new DaemonServer({ socketPath });
13
- await server.start();
14
- // Ready — any error that follows goes to the daemon log via stderr.
15
- }