wave-code 1.0.8 → 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 }) => {
@@ -14,6 +14,21 @@ export const useChat = () => {
14
14
  }
15
15
  return context;
16
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
+ });
17
32
  /**
18
33
  * Window-concat throttle for pure-delta streaming updates: chunks arriving
19
34
  * within the cooldown window are merged so no delta is lost (a dropped delta
@@ -322,7 +337,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
322
337
  // the incremental callbacks in initializeAgent below.
323
338
  const refreshMessages = useCallback(() => {
324
339
  if (!isExpandedRef.current && agentRef.current) {
325
- const msgs = [...agentRef.current.messages];
340
+ const msgs = agentRef.current.messages.map(snapshotMessage);
326
341
  setMessages(msgs);
327
342
  setLatestTotalTokens(extractLatestTotalTokens(msgs));
328
343
  }
@@ -357,7 +372,9 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
357
372
  const last = msgs[msgs.length - 1];
358
373
  if (!last || last.role !== "user")
359
374
  return;
360
- 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)]);
361
378
  },
362
379
  onAssistantMessageAdded: (messageId) => {
363
380
  if (isExpandedRef.current || !agentRef.current)
@@ -365,7 +382,9 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
365
382
  const msg = agentRef.current.messages.find((m) => m.id === messageId);
366
383
  if (!msg)
367
384
  return;
368
- 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)]);
369
388
  },
370
389
  onAssistantContentUpdated: (params) => {
371
390
  if (isExpandedRef.current)
@@ -562,9 +581,10 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
562
581
  };
563
582
  agent.setWorktreeSession(session);
564
583
  }
565
- // Get initial state
584
+ // Get initial state — snapshot the SDK messages (never hold live
585
+ // references; see snapshotMessage)
566
586
  setSessionId(agent.sessionId);
567
- setMessages(agent.messages);
587
+ setMessages(agent.messages.map(snapshotMessage));
568
588
  setIsLoading(agent.isLoading);
569
589
  setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
570
590
  setIsCommandRunning(agent.isCommandRunning);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-code",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "CLI-based code assistant powered by AI, built with React and Ink",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,7 +43,7 @@
43
43
  "wrap-ansi": "^10.0.0",
44
44
  "yargs": "^17.7.2",
45
45
  "zod": "^3.23.8",
46
- "wave-agent-sdk": "1.0.8"
46
+ "wave-agent-sdk": "1.0.9"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/react": "^19.1.8",
@@ -168,6 +168,22 @@ interface StreamingUpdateParams {
168
168
  stage: "streaming" | "end";
169
169
  }
170
170
 
171
+ /**
172
+ * Snapshot a SDK message for consumer state. The SDK mutates its internal
173
+ * message blocks in-place BEFORE firing the delta callback (it writes the full
174
+ * accumulated value to the shared block, then computes the chunk delta by
175
+ * slicing the new value). A consumer that pushed the SDK message object by
176
+ * live reference would read the already-updated block and append the delta
177
+ * again — the first delta is double-counted ("LetLet me think..."), affecting
178
+ * reasoning and text content alike. See docs/specs/core/stream-content-updates.md.
179
+ * The clone must be at least one layer deep (message + blocks) so the in-place
180
+ * block mutation never leaks into consumer state.
181
+ */
182
+ const snapshotMessage = (message: Message): Message => ({
183
+ ...message,
184
+ blocks: message.blocks.map((block) => ({ ...block })),
185
+ });
186
+
171
187
  /**
172
188
  * Window-concat throttle for pure-delta streaming updates: chunks arriving
173
189
  * within the cooldown window are merged so no delta is lost (a dropped delta
@@ -605,7 +621,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
605
621
  // the incremental callbacks in initializeAgent below.
606
622
  const refreshMessages = useCallback(() => {
607
623
  if (!isExpandedRef.current && agentRef.current) {
608
- const msgs = [...agentRef.current.messages];
624
+ const msgs = agentRef.current.messages.map(snapshotMessage);
609
625
  setMessages(msgs);
610
626
  setLatestTotalTokens(extractLatestTotalTokens(msgs));
611
627
  }
@@ -656,7 +672,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
656
672
  const last = msgs[msgs.length - 1];
657
673
  if (!last || last.role !== "user") return;
658
674
  setMessages((prev) =>
659
- prev.some((m) => m.id === last.id) ? prev : [...prev, last],
675
+ prev.some((m) => m.id === last.id)
676
+ ? prev
677
+ : [...prev, snapshotMessage(last)],
660
678
  );
661
679
  },
662
680
  onAssistantMessageAdded: (messageId: string) => {
@@ -664,7 +682,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
664
682
  const msg = agentRef.current.messages.find((m) => m.id === messageId);
665
683
  if (!msg) return;
666
684
  setMessages((prev) =>
667
- prev.some((m) => m.id === messageId) ? prev : [...prev, msg],
685
+ prev.some((m) => m.id === messageId)
686
+ ? prev
687
+ : [...prev, snapshotMessage(msg)],
668
688
  );
669
689
  },
670
690
  onAssistantContentUpdated: (params) => {
@@ -889,9 +909,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
889
909
  agent.setWorktreeSession(session);
890
910
  }
891
911
 
892
- // Get initial state
912
+ // Get initial state — snapshot the SDK messages (never hold live
913
+ // references; see snapshotMessage)
893
914
  setSessionId(agent.sessionId);
894
- setMessages(agent.messages);
915
+ setMessages(agent.messages.map(snapshotMessage));
895
916
  setIsLoading(agent.isLoading);
896
917
  setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
897
918
  setIsCommandRunning(agent.isCommandRunning);