wave-code 1.0.0 → 1.0.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 (70) hide show
  1. package/dist/cli.js +20 -1
  2. package/dist/components/App.js +7 -0
  3. package/dist/components/BtwDisplay.js +13 -3
  4. package/dist/components/ChatInterface.js +21 -6
  5. package/dist/components/InputBox.d.ts +0 -3
  6. package/dist/components/InputBox.js +11 -9
  7. package/dist/components/LoadingIndicator.d.ts +1 -2
  8. package/dist/components/LoadingIndicator.js +2 -2
  9. package/dist/components/Markdown.js +13 -16
  10. package/dist/components/MessageList.d.ts +2 -1
  11. package/dist/components/MessageList.js +2 -2
  12. package/dist/components/StatusLine.d.ts +0 -2
  13. package/dist/components/StatusLine.js +6 -6
  14. package/dist/components/TaskList.js +2 -1
  15. package/dist/components/ToolDisplay.d.ts +1 -0
  16. package/dist/components/ToolDisplay.js +17 -9
  17. package/dist/constants/commands.js +0 -6
  18. package/dist/contexts/useChat.d.ts +3 -5
  19. package/dist/contexts/useChat.js +242 -82
  20. package/dist/daemon-cli.d.ts +10 -0
  21. package/dist/daemon-cli.js +15 -0
  22. package/dist/hooks/useInputManager.js +99 -22
  23. package/dist/index.js +10 -0
  24. package/dist/managers/inputHandlers.js +50 -22
  25. package/dist/managers/inputReducer.d.ts +12 -2
  26. package/dist/managers/inputReducer.js +57 -9
  27. package/dist/stdio/agentBridge.d.ts +23 -0
  28. package/dist/stdio/agentBridge.js +126 -16
  29. package/dist/stdio/daemonServer.d.ts +67 -0
  30. package/dist/stdio/daemonServer.js +191 -0
  31. package/dist/stdio/index.d.ts +2 -0
  32. package/dist/stdio/index.js +2 -0
  33. package/dist/stdio/jsonRpcConnection.d.ts +30 -0
  34. package/dist/stdio/jsonRpcConnection.js +127 -0
  35. package/dist/stdio/protocol.d.ts +2 -2
  36. package/dist/stdio/stdioServer.d.ts +2 -7
  37. package/dist/stdio/stdioServer.js +9 -100
  38. package/dist/utils/bracketedPaste.d.ts +39 -0
  39. package/dist/utils/bracketedPaste.js +122 -0
  40. package/dist/utils/markdownTable.d.ts +34 -0
  41. package/dist/utils/markdownTable.js +302 -0
  42. package/dist/utils/throttle.d.ts +3 -3
  43. package/package.json +4 -2
  44. package/src/cli.tsx +20 -1
  45. package/src/components/App.tsx +5 -0
  46. package/src/components/BtwDisplay.tsx +36 -12
  47. package/src/components/ChatInterface.tsx +25 -12
  48. package/src/components/InputBox.tsx +10 -18
  49. package/src/components/LoadingIndicator.tsx +1 -4
  50. package/src/components/Markdown.tsx +15 -18
  51. package/src/components/MessageList.tsx +6 -0
  52. package/src/components/StatusLine.tsx +0 -10
  53. package/src/components/TaskList.tsx +2 -1
  54. package/src/components/ToolDisplay.tsx +17 -6
  55. package/src/constants/commands.ts +0 -6
  56. package/src/contexts/useChat.tsx +310 -95
  57. package/src/daemon-cli.ts +17 -0
  58. package/src/hooks/useInputManager.ts +108 -22
  59. package/src/index.ts +12 -0
  60. package/src/managers/inputHandlers.ts +49 -22
  61. package/src/managers/inputReducer.ts +66 -11
  62. package/src/stdio/agentBridge.ts +188 -17
  63. package/src/stdio/daemonServer.ts +212 -0
  64. package/src/stdio/index.ts +2 -0
  65. package/src/stdio/jsonRpcConnection.ts +160 -0
  66. package/src/stdio/protocol.ts +5 -2
  67. package/src/stdio/stdioServer.ts +14 -120
  68. package/src/utils/bracketedPaste.ts +170 -0
  69. package/src/utils/markdownTable.ts +359 -0
  70. package/src/utils/throttle.ts +8 -8
@@ -19,6 +19,7 @@ import type {
19
19
  PermissionMode,
20
20
  QueuedMessage,
21
21
  WorkflowRun,
22
+ ToolBlockUpdateCallbackParams,
22
23
  } from "wave-agent-sdk";
23
24
  import {
24
25
  Agent,
@@ -45,6 +46,9 @@ export interface ChatContextType {
45
46
  isExpanded: boolean;
46
47
  isTaskListVisible: boolean;
47
48
  setIsTaskListVisible: (visible: boolean) => void;
49
+ // True while the /btw side-question overlay is on display
50
+ isBtwActive: boolean;
51
+ setIsBtwActive: (active: boolean) => void;
48
52
  queuedMessages: QueuedMessage[];
49
53
  // AI functionality
50
54
  sessionId: string;
@@ -53,10 +57,13 @@ export interface ChatContextType {
53
57
  images?: Array<{ path: string; mimeType: string }>,
54
58
  longTextMap?: Record<string, string>,
55
59
  ) => Promise<void>;
56
- askBtw: (question: string) => Promise<string>;
60
+ askBtw: (
61
+ question: string,
62
+ abortSignal?: AbortSignal,
63
+ onContent?: (content: string) => void,
64
+ ) => Promise<string>;
57
65
  clearMessages: () => Promise<void>;
58
66
  compact: (instructions?: string) => Promise<void>;
59
- goalCommand: (args?: string) => Promise<void>;
60
67
  abortMessage: () => void;
61
68
  recallQueuedMessage: () => QueuedMessage | null;
62
69
  removeQueuedMessageById: (id: string) => boolean;
@@ -132,10 +139,6 @@ export interface ChatContextType {
132
139
  recreateAgent: () => void;
133
140
  // Trigger WorktreeRemove hook BEFORE agent destruction
134
141
  triggerWorktreeRemoveHook: (worktreePath: string) => Promise<void>;
135
- // Goal state
136
- isGoalActive: boolean;
137
- goalElapsed?: string;
138
- isGoalEvaluating: boolean;
139
142
  }
140
143
 
141
144
  const ChatContext = createContext<ChatContextType | null>(null);
@@ -175,39 +178,150 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
175
178
  const isExpandedRef = useRef(isExpanded);
176
179
 
177
180
  const [isTaskListVisible, setIsTaskListVisible] = useState(true);
181
+ const [isBtwActive, setIsBtwActive] = useState(false);
178
182
 
179
183
  const [messages, setMessages] = useState<Message[]>([]);
180
184
  const [latestTotalTokens, setLatestTotalTokens] = useState(0);
181
185
  const [maxInputTokens, setMaxInputTokens] = useState(200000);
182
186
 
183
- const throttledSetMessages = useMemo(
187
+ // Throttled incremental streaming updaters — 500ms leading+trailing, the same interval
188
+ // as the pre-incremental throttledSetMessages. `stage === "end"` flushes the final
189
+ // update immediately so completion results are never delayed.
190
+ const throttledContentUpdate = useMemo(
184
191
  () =>
185
192
  throttle(
186
- () => {
187
- if (!isExpandedRef.current && agentRef.current) {
188
- const msgs = [...agentRef.current.messages];
189
- setMessages(msgs);
190
- setLatestTotalTokens(extractLatestTotalTokens(msgs));
191
- }
193
+ (params: {
194
+ messageId: string;
195
+ accumulated: string;
196
+ stage: "streaming" | "end";
197
+ }) => {
198
+ const { messageId, accumulated, stage } = params;
199
+ setMessages((prev) =>
200
+ prev.map((m) => {
201
+ if (m.id !== messageId) return m;
202
+ const textBlockIndex = m.blocks.findIndex(
203
+ (b) => b.type === "text",
204
+ );
205
+ if (textBlockIndex === -1) {
206
+ return {
207
+ ...m,
208
+ blocks: [
209
+ ...m.blocks,
210
+ { type: "text", content: accumulated, stage },
211
+ ],
212
+ };
213
+ }
214
+ return {
215
+ ...m,
216
+ blocks: m.blocks.map((b, idx) =>
217
+ idx === textBlockIndex && b.type === "text"
218
+ ? { ...b, content: accumulated, stage }
219
+ : b,
220
+ ),
221
+ };
222
+ }),
223
+ );
224
+ },
225
+ 500,
226
+ ),
227
+ [],
228
+ );
229
+
230
+ const throttledReasoningUpdate = useMemo(
231
+ () =>
232
+ throttle(
233
+ (params: {
234
+ messageId: string;
235
+ accumulated: string;
236
+ stage: "streaming" | "end";
237
+ }) => {
238
+ const { messageId, accumulated, stage } = params;
239
+ setMessages((prev) =>
240
+ prev.map((m) => {
241
+ if (m.id !== messageId) return m;
242
+ const reasoningBlockIndex = m.blocks.findIndex(
243
+ (b) => b.type === "reasoning",
244
+ );
245
+ if (reasoningBlockIndex === -1) {
246
+ return {
247
+ ...m,
248
+ blocks: [
249
+ ...m.blocks,
250
+ { type: "reasoning", content: accumulated, stage },
251
+ ],
252
+ };
253
+ }
254
+ return {
255
+ ...m,
256
+ blocks: m.blocks.map((b, idx) =>
257
+ idx === reasoningBlockIndex && b.type === "reasoning"
258
+ ? { ...b, content: accumulated, stage }
259
+ : b,
260
+ ),
261
+ };
262
+ }),
263
+ );
192
264
  },
193
265
  500,
194
- { leading: true, trailing: true },
195
266
  ),
196
267
  [],
197
268
  );
198
269
 
270
+ const throttledToolBlockUpdate = useMemo(
271
+ () =>
272
+ throttle((params: ToolBlockUpdateCallbackParams) => {
273
+ const { messageId, id: toolBlockId, ...updates } = params;
274
+ setMessages((prev) =>
275
+ prev.map((m) => {
276
+ if (m.id !== messageId) return m;
277
+ const toolBlockIndex = m.blocks.findIndex(
278
+ (b) => b.type === "tool" && b.id === toolBlockId,
279
+ );
280
+ if (toolBlockIndex === -1) {
281
+ return {
282
+ ...m,
283
+ blocks: [
284
+ ...m.blocks,
285
+ {
286
+ type: "tool",
287
+ id: toolBlockId,
288
+ name: updates.name || "",
289
+ stage: updates.stage || "start",
290
+ parameters: updates.parameters || "",
291
+ result: updates.result || "",
292
+ ...updates,
293
+ },
294
+ ],
295
+ };
296
+ }
297
+ return {
298
+ ...m,
299
+ blocks: m.blocks.map((b, idx) =>
300
+ idx === toolBlockIndex && b.type === "tool"
301
+ ? { ...b, ...updates }
302
+ : b,
303
+ ),
304
+ };
305
+ }),
306
+ );
307
+ }, 500),
308
+ [],
309
+ );
310
+
199
311
  useEffect(() => {
200
312
  isExpandedRef.current = isExpanded;
201
313
  if (isExpanded) {
202
- throttledSetMessages.cancel();
314
+ // Cancel pending throttled updates so the frozen expanded view isn't overwritten
315
+ throttledContentUpdate.cancel();
316
+ throttledReasoningUpdate.cancel();
317
+ throttledToolBlockUpdate.cancel();
203
318
  }
204
- }, [isExpanded, throttledSetMessages]);
205
-
206
- useEffect(() => {
207
- return () => {
208
- throttledSetMessages.cancel();
209
- };
210
- }, [throttledSetMessages]);
319
+ }, [
320
+ isExpanded,
321
+ throttledContentUpdate,
322
+ throttledReasoningUpdate,
323
+ throttledToolBlockUpdate,
324
+ ]);
211
325
 
212
326
  const [isLoading, setIsLoading] = useState(false);
213
327
  const [sessionId, setSessionId] = useState("");
@@ -216,28 +330,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
216
330
  const [currentModel, setCurrentModelState] = useState("");
217
331
  const [configuredModels, setConfiguredModels] = useState<string[]>([]);
218
332
  const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]);
219
- const [isGoalActive, setIsGoalActive] = useState(false);
220
- const [goalElapsed, setGoalElapsed] = useState<string | undefined>();
221
- const [isGoalEvaluating, setIsGoalEvaluating] = useState(false);
222
- const goalStartedAt = useRef<number | null>(null);
223
-
224
- // Update goal elapsed time every 30s while active
225
- useEffect(() => {
226
- if (!isGoalActive || goalStartedAt.current === null) return;
227
- const formatElapsed = (ms: number): string => {
228
- const minutes = Math.floor(ms / 60000);
229
- if (minutes < 1) return "<1m";
230
- if (minutes < 60) return `${minutes}m`;
231
- const hours = Math.floor(minutes / 60);
232
- const remainingMin = minutes % 60;
233
- return `${hours}h${remainingMin}m`;
234
- };
235
- const update = () =>
236
- setGoalElapsed(formatElapsed(Date.now() - goalStartedAt.current!));
237
- update();
238
- const timer = setInterval(update, 30_000);
239
- return () => clearInterval(timer);
240
- }, [isGoalActive]);
241
333
 
242
334
  // MCP State
243
335
  const [mcpServerStatuses, setMcpServerStatuses] = useState<McpServerStatus[]>(
@@ -337,6 +429,17 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
337
429
 
338
430
  const agentRef = useRef<Agent | null>(null);
339
431
 
432
+ // Full-list refresh — one-shot pull from the agent, used only for structural
433
+ // changes (compact/clear/rewind/collapse/init). Streaming updates flow through
434
+ // the incremental callbacks in initializeAgent below.
435
+ const refreshMessages = useCallback(() => {
436
+ if (!isExpandedRef.current && agentRef.current) {
437
+ const msgs = [...agentRef.current.messages];
438
+ setMessages(msgs);
439
+ setLatestTotalTokens(extractLatestTotalTokens(msgs));
440
+ }
441
+ }, []);
442
+
340
443
  // Permission confirmation methods with queue support
341
444
  const showConfirmation = useCallback(
342
445
  async (
@@ -371,8 +474,129 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
371
474
  restoreSessionIdOverride ?? restoreSessionId;
372
475
 
373
476
  const callbacks: AgentCallbacks = {
374
- onMessagesChange: () => {
375
- throttledSetMessages();
477
+ // ── Incremental message updates (no full-list pushes) ──────
478
+ onUserMessageAdded: () => {
479
+ if (isExpandedRef.current || !agentRef.current) return;
480
+ const msgs = agentRef.current.messages;
481
+ const last = msgs[msgs.length - 1];
482
+ if (!last || last.role !== "user") return;
483
+ setMessages((prev) =>
484
+ prev.some((m) => m.id === last.id) ? prev : [...prev, last],
485
+ );
486
+ },
487
+ onAssistantMessageAdded: (messageId: string) => {
488
+ if (isExpandedRef.current || !agentRef.current) return;
489
+ const msg = agentRef.current.messages.find((m) => m.id === messageId);
490
+ if (!msg) return;
491
+ setMessages((prev) =>
492
+ prev.some((m) => m.id === messageId) ? prev : [...prev, msg],
493
+ );
494
+ },
495
+ onAssistantContentUpdated: (params) => {
496
+ if (isExpandedRef.current) return;
497
+ throttledContentUpdate(params);
498
+ if (params.stage === "end") throttledContentUpdate.flush();
499
+ },
500
+ onAssistantReasoningUpdated: (params) => {
501
+ if (isExpandedRef.current) return;
502
+ throttledReasoningUpdate(params);
503
+ if (params.stage === "end") throttledReasoningUpdate.flush();
504
+ },
505
+ onToolBlockUpdated: (params) => {
506
+ if (isExpandedRef.current) return;
507
+ throttledToolBlockUpdate(params);
508
+ if (params.stage === "end") throttledToolBlockUpdate.flush();
509
+ },
510
+ onErrorBlockAdded: (error: string) => {
511
+ if (isExpandedRef.current) return;
512
+ setMessages((prev) => {
513
+ // Append to the last assistant message, or create one if none exists
514
+ for (let i = prev.length - 1; i >= 0; i--) {
515
+ if (prev[i].role === "assistant") {
516
+ return prev.map((m, idx) =>
517
+ idx === i
518
+ ? {
519
+ ...m,
520
+ blocks: [
521
+ ...m.blocks,
522
+ { type: "error", content: error },
523
+ ],
524
+ }
525
+ : m,
526
+ );
527
+ }
528
+ }
529
+ return [
530
+ ...prev,
531
+ {
532
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
533
+ role: "assistant",
534
+ timestamp: new Date().toISOString(),
535
+ blocks: [{ type: "error", content: error }],
536
+ },
537
+ ];
538
+ });
539
+ },
540
+ onAddBangMessage: (command, messageId) => {
541
+ if (isExpandedRef.current) return;
542
+ setMessages((prev) =>
543
+ prev.some((m) => m.id === messageId)
544
+ ? prev
545
+ : [
546
+ ...prev,
547
+ {
548
+ id: messageId,
549
+ role: "user",
550
+ timestamp: new Date().toISOString(),
551
+ blocks: [
552
+ {
553
+ type: "bang",
554
+ command,
555
+ output: "",
556
+ stage: "running",
557
+ exitCode: null,
558
+ },
559
+ ],
560
+ },
561
+ ],
562
+ );
563
+ },
564
+ onUpdateBangMessage: (command, output, messageId) => {
565
+ if (isExpandedRef.current) return;
566
+ setMessages((prev) =>
567
+ prev.map((m) =>
568
+ m.id === messageId
569
+ ? {
570
+ ...m,
571
+ blocks: m.blocks.map((b, idx) =>
572
+ idx === m.blocks.length - 1 && b.type === "bang"
573
+ ? { ...b, command, output }
574
+ : b,
575
+ ),
576
+ }
577
+ : m,
578
+ ),
579
+ );
580
+ },
581
+ onCompleteBangMessage: (command, exitCode, messageId) => {
582
+ if (isExpandedRef.current) return;
583
+ setMessages((prev) =>
584
+ prev.map((m) =>
585
+ m.id === messageId
586
+ ? {
587
+ ...m,
588
+ blocks: m.blocks.map((b, idx) =>
589
+ idx === m.blocks.length - 1 && b.type === "bang"
590
+ ? { ...b, command, exitCode, stage: "end" }
591
+ : b,
592
+ ),
593
+ }
594
+ : m,
595
+ ),
596
+ );
597
+ },
598
+ onLatestTotalTokensChange: (tokens) => {
599
+ setLatestTotalTokens(tokens);
376
600
  },
377
601
  onMcpServersChange: (servers) => {
378
602
  setMcpServerStatuses([...servers]);
@@ -421,18 +645,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
421
645
  onQueuedMessagesChange: (messages) => {
422
646
  setQueuedMessages([...messages]);
423
647
  },
424
- onGoalStateChange: (active, _condition, elapsed) => {
425
- setIsGoalActive(active);
426
- if (active) {
427
- goalStartedAt.current = Date.now();
428
- } else {
429
- goalStartedAt.current = null;
430
- }
431
- setGoalElapsed(elapsed);
432
- },
433
- onGoalEvaluating: (evaluating) => {
434
- setIsGoalEvaluating(evaluating);
435
- },
436
648
  };
437
649
 
438
650
  try {
@@ -533,7 +745,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
533
745
  originalCwd,
534
746
  model,
535
747
  initialPermissionMode,
536
- throttledSetMessages,
748
+ refreshMessages,
749
+ throttledContentUpdate,
750
+ throttledReasoningUpdate,
751
+ throttledToolBlockUpdate,
537
752
  mcpServers,
538
753
  ],
539
754
  );
@@ -571,6 +786,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
571
786
  // Cleanup on unmount
572
787
  useEffect(() => {
573
788
  return () => {
789
+ throttledContentUpdate.cancel();
790
+ throttledReasoningUpdate.cancel();
791
+ throttledToolBlockUpdate.cancel();
574
792
  if (agentRef.current) {
575
793
  try {
576
794
  // Display usage summary before cleanup
@@ -584,7 +802,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
584
802
  agentRef.current.destroy();
585
803
  }
586
804
  };
587
- }, []);
805
+ }, [
806
+ throttledContentUpdate,
807
+ throttledReasoningUpdate,
808
+ throttledToolBlockUpdate,
809
+ ]);
588
810
 
589
811
  // Trigger WorktreeRemove hook BEFORE agent destruction
590
812
  const triggerWorktreeRemoveHook = useCallback(
@@ -637,33 +859,32 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
637
859
  [],
638
860
  );
639
861
 
640
- const askBtw = useCallback(async (question: string) => {
641
- if (!agentRef.current) {
642
- throw new Error("Agent not initialized");
643
- }
644
- return await agentRef.current.askBtw(question);
645
- }, []);
862
+ const askBtw = useCallback(
863
+ async (
864
+ question: string,
865
+ abortSignal?: AbortSignal,
866
+ onContent?: (content: string) => void,
867
+ ) => {
868
+ if (!agentRef.current) {
869
+ throw new Error("Agent not initialized");
870
+ }
871
+ return await agentRef.current.askBtw(question, abortSignal, onContent);
872
+ },
873
+ [],
874
+ );
646
875
 
647
876
  const clearMessages = useCallback(async () => {
648
877
  await agentRef.current?.clearMessages();
649
- }, []);
878
+ refreshMessages();
879
+ }, [refreshMessages]);
650
880
 
651
- const compact = useCallback(async (instructions?: string) => {
652
- await agentRef.current?.compact(instructions);
653
- }, []);
654
-
655
- const goalCommand = useCallback(async (args?: string) => {
656
- const trimmed = args?.trim() ?? "";
657
- if (!trimmed) {
658
- await agentRef.current?.showGoalStatus();
659
- } else if (
660
- ["clear", "stop", "off", "reset", "none", "cancel"].includes(trimmed)
661
- ) {
662
- await agentRef.current?.clearGoal();
663
- } else {
664
- await agentRef.current?.setGoal(trimmed);
665
- }
666
- }, []);
881
+ const compact = useCallback(
882
+ async (instructions?: string) => {
883
+ await agentRef.current?.compact(instructions);
884
+ refreshMessages();
885
+ },
886
+ [refreshMessages],
887
+ );
667
888
 
668
889
  // Unified interrupt method, interrupt both AI messages and command execution
669
890
  const abortMessage = useCallback(() => {
@@ -773,13 +994,14 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
773
994
  if (agentRef.current) {
774
995
  try {
775
996
  await agentRef.current.truncateHistory(index);
997
+ refreshMessages();
776
998
  requestRemount();
777
999
  } catch (error) {
778
1000
  logger.error("Failed to rewind:", error);
779
1001
  }
780
1002
  }
781
1003
  },
782
- [requestRemount],
1004
+ [requestRemount, refreshMessages],
783
1005
  );
784
1006
 
785
1007
  const getFullMessageThread = useCallback(async () => {
@@ -827,15 +1049,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
827
1049
 
828
1050
  if (nextExpanded) {
829
1051
  // Transitioning to EXPANDED: Freeze the current view
830
- // Cancel any pending throttled updates to avoid overwriting the frozen state
831
- throttledSetMessages.cancel();
1052
+ // Incremental updates are skipped while expanded (isExpandedRef guard)
832
1053
  } else {
833
1054
  // Transitioning to COLLAPSED: Restore from agent's actual state
834
- if (agentRef.current) {
835
- const msgs = [...agentRef.current.messages];
836
- setMessages(msgs);
837
- setLatestTotalTokens(extractLatestTotalTokens(msgs));
838
- }
1055
+ refreshMessages();
839
1056
  }
840
1057
  // Force remount directly (bypass throttle) to ensure Static items re-render
841
1058
  // The throttled requestRemount can be dropped if pressed too quickly after
@@ -862,13 +1079,14 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
862
1079
  isExpanded,
863
1080
  isTaskListVisible,
864
1081
  setIsTaskListVisible,
1082
+ isBtwActive,
1083
+ setIsBtwActive,
865
1084
  queuedMessages,
866
1085
  sessionId,
867
1086
  sendMessage,
868
1087
  askBtw,
869
1088
  clearMessages,
870
1089
  compact,
871
- goalCommand,
872
1090
  abortMessage,
873
1091
  recallQueuedMessage,
874
1092
  removeQueuedMessageById,
@@ -912,9 +1130,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
912
1130
  workdir,
913
1131
  recreateAgent,
914
1132
  triggerWorktreeRemoveHook,
915
- isGoalActive,
916
- goalElapsed,
917
- isGoalEvaluating,
918
1133
  };
919
1134
 
920
1135
  return (
@@ -0,0 +1,17 @@
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
+
11
+ import { DaemonServer } from "./stdio/daemonServer.js";
12
+
13
+ export async function startDaemonCli(socketPath: string): Promise<void> {
14
+ const server = new DaemonServer({ socketPath });
15
+ await server.start();
16
+ // Ready — any error that follows goes to the daemon log via stderr.
17
+ }