wave-code 1.0.7 → 1.0.9

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 CHANGED
@@ -1,5 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { readFileSync, writeSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ // `wave -v` / `wave --version` must be fast: editors probe the installed CLI
8
+ // version on every launch (e.g. the desktop app's auto-update check). Loading
9
+ // the full app graph (wave-agent-sdk, ink, highlight.js, ...) just to print
10
+ // the version takes 2-3s+ on a warm machine and can exceed callers' probe
11
+ // timeouts on cold starts (AV scan of freshly installed files), which they
12
+ // misread as "CLI missing/corrupt" → spurious re-installs. Print the version
13
+ // straight from package.json and exit before touching the app graph.
14
+ const versionArgs = ["-v", "--version"];
15
+ if (process.argv.slice(2).some((a) => versionArgs.includes(a))) {
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const packageJson = JSON.parse(
18
+ readFileSync(path.resolve(__dirname, "../package.json"), "utf-8"),
19
+ );
20
+ writeSync(1, `${packageJson.version}\n`);
21
+ process.exit(0);
22
+ }
23
+
3
24
  // Import and start the CLI
4
25
  import("../dist/index.js")
5
26
  .then(async ({ main }) => {
@@ -57,5 +57,5 @@ export const ChatInterface = () => {
57
57
  ]);
58
58
  if (!sessionId)
59
59
  return null;
60
- return (_jsxs(Box, { ref: chatInterfaceRef, flexDirection: "column", children: [_jsx(MessageList, { messages: displayMessages, isExpanded: isExpanded, version: version, workdir: workdir, forceStatic: forceStatic }, remountKey), !isConfirmationVisible && !isExpanded && (_jsxs(_Fragment, { children: [(isLoading || isCommandRunning || isCompacting) && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, latestTotalTokens: latestTotalTokens })), _jsx(TaskList, {}), _jsx(QueuedMessageList, {}), _jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand, latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens, showLoginHint: showLoginHint })] })), isConfirmationVisible && (_jsxs(_Fragment, { children: [forceStatic ? (_jsx(Static, { items: [{ key: "confirmation-details" }], children: () => (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, isExpanded: isExpanded }, "confirmation-details")) })) : (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, isExpanded: isExpanded })), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, permissionMode: confirmingTool.permissionMode, isExpanded: isExpanded, onDecision: handleConfirmationDecision, onCancel: handleConfirmationCancel })] }))] }));
60
+ return (_jsxs(Box, { ref: chatInterfaceRef, flexDirection: "column", children: [_jsx(MessageList, { messages: displayMessages, isExpanded: isExpanded, version: version, workdir: workdir, forceStatic: forceStatic }, remountKey), !isConfirmationVisible && !isExpanded && (_jsxs(_Fragment, { children: [(isLoading || isCommandRunning || isCompacting) && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, latestTotalTokens: latestTotalTokens })), _jsx(TaskList, {}), _jsx(QueuedMessageList, {}), _jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompacting: isCompacting, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand, latestTotalTokens: latestTotalTokens, maxInputTokens: maxInputTokens, showLoginHint: showLoginHint })] })), isConfirmationVisible && (_jsxs(_Fragment, { children: [forceStatic ? (_jsx(Static, { items: [{ key: "confirmation-details" }], children: () => (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, warning: confirmingTool.warning, isExpanded: isExpanded }, "confirmation-details")) })) : (_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, planContent: confirmingTool.planContent, warning: confirmingTool.warning, isExpanded: isExpanded })), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, permissionMode: confirmingTool.permissionMode, isExpanded: isExpanded, onDecision: handleConfirmationDecision, onCancel: handleConfirmationCancel })] }))] }));
61
61
  };
@@ -3,6 +3,7 @@ export interface ConfirmationDetailsProps {
3
3
  toolName: string;
4
4
  toolInput?: Record<string, unknown>;
5
5
  planContent?: string;
6
+ warning?: string;
6
7
  isExpanded?: boolean;
7
8
  }
8
9
  export declare const ConfirmationDetails: React.FC<ConfirmationDetailsProps>;
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
- import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
3
+ import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, ARTIFACT_TOOL_NAME, } from "wave-agent-sdk";
4
4
  import { DiffDisplay } from "./DiffDisplay.js";
5
5
  import { PlanDisplay } from "./PlanDisplay.js";
6
6
  import { highlightToAnsi } from "../utils/highlightUtils.js";
@@ -22,14 +22,16 @@ const getActionDescription = (toolName, toolInput) => {
22
22
  return "Enter plan mode for complex task planning";
23
23
  case ASK_USER_QUESTION_TOOL_NAME:
24
24
  return "Answer questions to clarify intent";
25
+ case ARTIFACT_TOOL_NAME:
26
+ return `Publish file: ${toolInput.file_path || "unknown file"}`;
25
27
  default:
26
28
  return "Execute operation";
27
29
  }
28
30
  };
29
- export const ConfirmationDetails = ({ toolName, toolInput, planContent, isExpanded = false, }) => {
31
+ export const ConfirmationDetails = ({ toolName, toolInput, planContent, warning, isExpanded = false, }) => {
30
32
  const startLineNumber = toolInput?.startLineNumber ??
31
33
  (toolName === WRITE_TOOL_NAME ? 1 : undefined);
32
- const content = (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), _jsx(DiffDisplay, { toolName: toolName, parameters: JSON.stringify(toolInput), startLineNumber: startLineNumber }), toolName !== WRITE_TOOL_NAME &&
34
+ const content = (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), warning && _jsxs(Text, { color: "red", children: ["\u26A0 ", warning] }), _jsx(DiffDisplay, { toolName: toolName, parameters: JSON.stringify(toolInput), startLineNumber: startLineNumber }), toolName !== WRITE_TOOL_NAME &&
33
35
  toolName !== EDIT_TOOL_NAME &&
34
36
  toolName !== EXIT_PLAN_MODE_TOOL_NAME &&
35
37
  toolName !== ENTER_PLAN_MODE_TOOL_NAME &&
@@ -16,10 +16,17 @@ export const RewindCommand = ({ messages: initialMessages, onSelect, onCancel, g
16
16
  }
17
17
  }, [getFullMessageThread]);
18
18
  // Filter user messages as checkpoints, excluding meta messages and
19
- // system-generated user-role messages (task notifications, hook injections)
20
- const checkpoints = messages
21
- .map((msg, index) => ({ msg, index }))
22
- .filter(({ msg }) => isUserCheckpointMessage(msg));
19
+ // system-generated user-role messages (task notifications, hook injections).
20
+ // Compaction is append-only: the same message id appears twice on the full
21
+ // thread (pre-compact history + post-compact append), so dedupe by id and
22
+ // keep the last occurrence (matching the folded view the user sees).
23
+ const checkpointMap = new Map();
24
+ messages.forEach((msg, index) => {
25
+ if (!isUserCheckpointMessage(msg))
26
+ return;
27
+ checkpointMap.set(msg.id ?? `index:${index}`, { msg, index });
28
+ });
29
+ const checkpoints = Array.from(checkpointMap.values());
23
30
  const MAX_VISIBLE_ITEMS = 3;
24
31
  const [state, dispatch] = useReducer(rewindSelectorReducer, {
25
32
  selectedIndex: checkpoints.length - 1,
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import type { Message, McpServerStatus, BackgroundTask, Task, SlashCommand, SubagentConfiguration, PermissionDecision, PermissionMode, QueuedMessage, WorkflowRun } from "wave-agent-sdk";
2
+ import type { Message, McpServerStatus, BackgroundTask, Task, SlashCommand, SubagentConfiguration, PermissionDecision, PermissionMode, QueuedMessage, WorkflowRun, ToolBlockUpdateCallbackParams } from "wave-agent-sdk";
3
3
  import { BaseAppProps } from "../types.js";
4
4
  export interface ChatContextType {
5
5
  messages: Message[];
@@ -58,8 +58,9 @@ export interface ChatContextType {
58
58
  hidePersistentOption?: boolean;
59
59
  planContent?: string;
60
60
  permissionMode?: PermissionMode;
61
+ warning?: string;
61
62
  };
62
- showConfirmation: (toolName: string, toolInput?: Record<string, unknown>, suggestedPrefix?: string, hidePersistentOption?: boolean, planContent?: string, permissionMode?: PermissionMode) => Promise<PermissionDecision>;
63
+ showConfirmation: (toolName: string, toolInput?: Record<string, unknown>, suggestedPrefix?: string, hidePersistentOption?: boolean, planContent?: string, permissionMode?: PermissionMode, warning?: string) => Promise<PermissionDecision>;
63
64
  hideConfirmation: () => void;
64
65
  handleConfirmationDecision: (decision: PermissionDecision) => void;
65
66
  handleConfirmationCancel: () => void;
@@ -83,4 +84,18 @@ export declare const useChat: () => ChatContextType;
83
84
  export interface ChatProviderProps extends BaseAppProps {
84
85
  children: React.ReactNode;
85
86
  }
87
+ /**
88
+ * Per-tool window-concat throttle for pure-delta tool parameter streaming:
89
+ * `parametersChunk` deltas are accumulated independently per tool block id
90
+ * within the cooldown window, so interleaved multi-tool streams lose no delta
91
+ * (a plain throttle's single last-args slot would drop every earlier tool's
92
+ * deltas, leaving the first tool without streaming parameters). `start` /
93
+ * `running` apply immediately (one-shot snapshots); `end` flushes pending
94
+ * streaming deltas first, then applies the authoritative parameters/result.
95
+ */
96
+ export declare function createToolStreamingThrottle(fn: (params: ToolBlockUpdateCallbackParams) => void, wait: number): {
97
+ (params: ToolBlockUpdateCallbackParams): void;
98
+ cancel: () => void;
99
+ flush: () => void;
100
+ };
86
101
  export declare const ChatProvider: React.FC<ChatProviderProps>;
@@ -4,7 +4,6 @@ import { useInput, useStdout } from "ink";
4
4
  import { useAppConfig } from "./useAppConfig.js";
5
5
  import { Agent, OPERATION_CANCELLED_BY_USER, extractLatestTotalTokens, } from "wave-agent-sdk";
6
6
  import { logger } from "../utils/logger.js";
7
- import { throttle } from "../utils/throttle.js";
8
7
  import { displayUsageSummary } from "../utils/usageSummary.js";
9
8
  import { expandLongTextPlaceholders } from "../managers/inputHandlers.js";
10
9
  const ChatContext = createContext(null);
@@ -15,6 +14,21 @@ export const useChat = () => {
15
14
  }
16
15
  return context;
17
16
  };
17
+ /**
18
+ * Snapshot a SDK message for consumer state. The SDK mutates its internal
19
+ * message blocks in-place BEFORE firing the delta callback (it writes the full
20
+ * accumulated value to the shared block, then computes the chunk delta by
21
+ * slicing the new value). A consumer that pushed the SDK message object by
22
+ * live reference would read the already-updated block and append the delta
23
+ * again — the first delta is double-counted ("LetLet me think..."), affecting
24
+ * reasoning and text content alike. See docs/specs/core/stream-content-updates.md.
25
+ * The clone must be at least one layer deep (message + blocks) so the in-place
26
+ * block mutation never leaks into consumer state.
27
+ */
28
+ const snapshotMessage = (message) => ({
29
+ ...message,
30
+ blocks: message.blocks.map((block) => ({ ...block })),
31
+ });
18
32
  /**
19
33
  * Window-concat throttle for pure-delta streaming updates: chunks arriving
20
34
  * within the cooldown window are merged so no delta is lost (a dropped delta
@@ -75,6 +89,86 @@ function createStreamingWindowThrottle(fn, wait) {
75
89
  };
76
90
  return throttled;
77
91
  }
92
+ /**
93
+ * Per-tool window-concat throttle for pure-delta tool parameter streaming:
94
+ * `parametersChunk` deltas are accumulated independently per tool block id
95
+ * within the cooldown window, so interleaved multi-tool streams lose no delta
96
+ * (a plain throttle's single last-args slot would drop every earlier tool's
97
+ * deltas, leaving the first tool without streaming parameters). `start` /
98
+ * `running` apply immediately (one-shot snapshots); `end` flushes pending
99
+ * streaming deltas first, then applies the authoritative parameters/result.
100
+ */
101
+ export function createToolStreamingThrottle(fn, wait) {
102
+ let timer = null;
103
+ let pending = null;
104
+ const fire = () => {
105
+ if (pending && pending.chunks.size > 0) {
106
+ const { messageId, chunks } = pending;
107
+ pending = null;
108
+ for (const [id, chunk] of chunks) {
109
+ fn({ messageId, id, parametersChunk: chunk, stage: "streaming" });
110
+ }
111
+ }
112
+ };
113
+ const throttled = (params) => {
114
+ if (params.stage === "end") {
115
+ // Flush any deltas still pending inside the cooldown window first
116
+ if (timer) {
117
+ clearTimeout(timer);
118
+ timer = null;
119
+ }
120
+ fire();
121
+ fn(params);
122
+ return;
123
+ }
124
+ if (params.stage === "streaming") {
125
+ if (!pending) {
126
+ pending = { messageId: params.messageId, chunks: new Map() };
127
+ }
128
+ const prev = pending.chunks.get(params.id) || "";
129
+ pending.chunks.set(params.id, prev + (params.parametersChunk || ""));
130
+ if (!timer) {
131
+ timer = setTimeout(() => {
132
+ timer = null;
133
+ fire();
134
+ }, wait);
135
+ }
136
+ return;
137
+ }
138
+ // start / running — one-shot snapshots applied immediately. Drop this
139
+ // tool's buffered streaming deltas first: start/running carry the
140
+ // authoritative parameters, and a pending timer would otherwise fire late
141
+ // with a stale `streaming` event, regressing this tool block's stage back
142
+ // to streaming (yellow dot -> gray) mid-execution. Other tools' in-flight
143
+ // chunks are kept so interleaved multi-tool streaming still accumulates.
144
+ if (pending) {
145
+ pending.chunks.delete(params.id);
146
+ if (pending.chunks.size === 0) {
147
+ pending = null;
148
+ if (timer) {
149
+ clearTimeout(timer);
150
+ timer = null;
151
+ }
152
+ }
153
+ }
154
+ fn(params);
155
+ };
156
+ throttled.cancel = () => {
157
+ if (timer) {
158
+ clearTimeout(timer);
159
+ timer = null;
160
+ }
161
+ pending = null;
162
+ };
163
+ throttled.flush = () => {
164
+ if (timer) {
165
+ clearTimeout(timer);
166
+ timer = null;
167
+ }
168
+ fire();
169
+ };
170
+ return throttled;
171
+ }
78
172
  export const ChatProvider = ({ children, bypassPermissions, permissionMode: initialPermissionMode, pluginDirs, additionalDirectories, tools, allowedTools, disallowedTools, workdir, worktreeSession, originalCwd, version, model, mcpServers, }) => {
79
173
  const { restoreSessionId, continueLastSession } = useAppConfig();
80
174
  const { stdout } = useStdout();
@@ -142,8 +236,8 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
142
236
  };
143
237
  }));
144
238
  }, 500), []);
145
- const throttledToolBlockUpdate = useMemo(() => throttle((params) => {
146
- const { messageId, id: toolBlockId, ...updates } = params;
239
+ const throttledToolBlockUpdate = useMemo(() => createToolStreamingThrottle((params) => {
240
+ const { messageId, id: toolBlockId, parametersChunk, ...updates } = params;
147
241
  setMessages((prev) => prev.map((m) => {
148
242
  if (m.id !== messageId)
149
243
  return m;
@@ -158,7 +252,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
158
252
  id: toolBlockId,
159
253
  name: updates.name || "",
160
254
  stage: updates.stage || "start",
161
- parameters: updates.parameters || "",
255
+ parameters: (updates.parameters || "") + (parametersChunk || ""),
162
256
  result: updates.result || "",
163
257
  ...updates,
164
258
  },
@@ -168,7 +262,18 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
168
262
  return {
169
263
  ...m,
170
264
  blocks: m.blocks.map((b, idx) => idx === toolBlockIndex && b.type === "tool"
171
- ? { ...b, ...updates }
265
+ ? {
266
+ ...b,
267
+ ...updates,
268
+ // Streaming carries only the delta; append it to the
269
+ // accumulated parameters. start/running/end carry the
270
+ // authoritative value and replace wholesale.
271
+ parameters: parametersChunk
272
+ ? (b.parameters || "") + parametersChunk
273
+ : updates.parameters !== undefined
274
+ ? updates.parameters
275
+ : b.parameters,
276
+ }
172
277
  : b),
173
278
  };
174
279
  }));
@@ -232,13 +337,13 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
232
337
  // the incremental callbacks in initializeAgent below.
233
338
  const refreshMessages = useCallback(() => {
234
339
  if (!isExpandedRef.current && agentRef.current) {
235
- const msgs = [...agentRef.current.messages];
340
+ const msgs = agentRef.current.messages.map(snapshotMessage);
236
341
  setMessages(msgs);
237
342
  setLatestTotalTokens(extractLatestTotalTokens(msgs));
238
343
  }
239
344
  }, []);
240
345
  // Permission confirmation methods with queue support
241
- const showConfirmation = useCallback(async (toolName, toolInput, suggestedPrefix, hidePersistentOption, planContent, permissionMode) => {
346
+ const showConfirmation = useCallback(async (toolName, toolInput, suggestedPrefix, hidePersistentOption, planContent, permissionMode, warning) => {
242
347
  return new Promise((resolve, reject) => {
243
348
  const queueItem = {
244
349
  toolName,
@@ -247,6 +352,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
247
352
  hidePersistentOption,
248
353
  planContent,
249
354
  permissionMode,
355
+ warning,
250
356
  resolver: resolve,
251
357
  reject,
252
358
  };
@@ -266,7 +372,9 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
266
372
  const last = msgs[msgs.length - 1];
267
373
  if (!last || last.role !== "user")
268
374
  return;
269
- setMessages((prev) => prev.some((m) => m.id === last.id) ? prev : [...prev, last]);
375
+ setMessages((prev) => prev.some((m) => m.id === last.id)
376
+ ? prev
377
+ : [...prev, snapshotMessage(last)]);
270
378
  },
271
379
  onAssistantMessageAdded: (messageId) => {
272
380
  if (isExpandedRef.current || !agentRef.current)
@@ -274,7 +382,9 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
274
382
  const msg = agentRef.current.messages.find((m) => m.id === messageId);
275
383
  if (!msg)
276
384
  return;
277
- setMessages((prev) => prev.some((m) => m.id === messageId) ? prev : [...prev, msg]);
385
+ setMessages((prev) => prev.some((m) => m.id === messageId)
386
+ ? prev
387
+ : [...prev, snapshotMessage(msg)]);
278
388
  },
279
389
  onAssistantContentUpdated: (params) => {
280
390
  if (isExpandedRef.current)
@@ -426,7 +536,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
426
536
  // Create the permission callback inside the try block to access showConfirmation
427
537
  const permissionCallback = async (context) => {
428
538
  try {
429
- return await showConfirmation(context.toolName, context.toolInput, context.suggestedPrefix, context.hidePersistentOption, context.planContent, context.permissionMode);
539
+ return await showConfirmation(context.toolName, context.toolInput, context.suggestedPrefix, context.hidePersistentOption, context.planContent, context.permissionMode, context.warning);
430
540
  }
431
541
  catch {
432
542
  // If confirmation was cancelled or failed, deny the operation
@@ -471,9 +581,10 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
471
581
  };
472
582
  agent.setWorktreeSession(session);
473
583
  }
474
- // Get initial state
584
+ // Get initial state — snapshot the SDK messages (never hold live
585
+ // references; see snapshotMessage)
475
586
  setSessionId(agent.sessionId);
476
- setMessages(agent.messages);
587
+ setMessages(agent.messages.map(snapshotMessage));
477
588
  setIsLoading(agent.isLoading);
478
589
  setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
479
590
  setIsCommandRunning(agent.isCommandRunning);
@@ -718,6 +829,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
718
829
  hidePersistentOption: next.hidePersistentOption,
719
830
  planContent: next.planContent,
720
831
  permissionMode: next.permissionMode,
832
+ warning: next.warning,
721
833
  });
722
834
  setIsConfirmationVisible(true);
723
835
  setConfirmationQueue((prev) => prev.slice(1));
@@ -0,0 +1,49 @@
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>;