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.
- package/dist/cli.js +20 -1
- package/dist/components/App.js +7 -0
- package/dist/components/BtwDisplay.js +13 -3
- package/dist/components/ChatInterface.js +21 -6
- package/dist/components/InputBox.d.ts +0 -3
- package/dist/components/InputBox.js +11 -9
- package/dist/components/LoadingIndicator.d.ts +1 -2
- package/dist/components/LoadingIndicator.js +2 -2
- package/dist/components/Markdown.js +13 -16
- package/dist/components/MessageList.d.ts +2 -1
- package/dist/components/MessageList.js +2 -2
- package/dist/components/StatusLine.d.ts +0 -2
- package/dist/components/StatusLine.js +6 -6
- package/dist/components/TaskList.js +2 -1
- package/dist/components/ToolDisplay.d.ts +1 -0
- package/dist/components/ToolDisplay.js +17 -9
- package/dist/constants/commands.js +0 -6
- package/dist/contexts/useChat.d.ts +3 -5
- package/dist/contexts/useChat.js +242 -82
- package/dist/daemon-cli.d.ts +10 -0
- package/dist/daemon-cli.js +15 -0
- package/dist/hooks/useInputManager.js +99 -22
- package/dist/index.js +10 -0
- package/dist/managers/inputHandlers.js +50 -22
- package/dist/managers/inputReducer.d.ts +12 -2
- package/dist/managers/inputReducer.js +57 -9
- package/dist/stdio/agentBridge.d.ts +23 -0
- package/dist/stdio/agentBridge.js +126 -16
- package/dist/stdio/daemonServer.d.ts +67 -0
- package/dist/stdio/daemonServer.js +191 -0
- package/dist/stdio/index.d.ts +2 -0
- package/dist/stdio/index.js +2 -0
- package/dist/stdio/jsonRpcConnection.d.ts +30 -0
- package/dist/stdio/jsonRpcConnection.js +127 -0
- package/dist/stdio/protocol.d.ts +2 -2
- package/dist/stdio/stdioServer.d.ts +2 -7
- package/dist/stdio/stdioServer.js +9 -100
- package/dist/utils/bracketedPaste.d.ts +39 -0
- package/dist/utils/bracketedPaste.js +122 -0
- package/dist/utils/markdownTable.d.ts +34 -0
- package/dist/utils/markdownTable.js +302 -0
- package/dist/utils/throttle.d.ts +3 -3
- package/package.json +4 -2
- package/src/cli.tsx +20 -1
- package/src/components/App.tsx +5 -0
- package/src/components/BtwDisplay.tsx +36 -12
- package/src/components/ChatInterface.tsx +25 -12
- package/src/components/InputBox.tsx +10 -18
- package/src/components/LoadingIndicator.tsx +1 -4
- package/src/components/Markdown.tsx +15 -18
- package/src/components/MessageList.tsx +6 -0
- package/src/components/StatusLine.tsx +0 -10
- package/src/components/TaskList.tsx +2 -1
- package/src/components/ToolDisplay.tsx +17 -6
- package/src/constants/commands.ts +0 -6
- package/src/contexts/useChat.tsx +310 -95
- package/src/daemon-cli.ts +17 -0
- package/src/hooks/useInputManager.ts +108 -22
- package/src/index.ts +12 -0
- package/src/managers/inputHandlers.ts +49 -22
- package/src/managers/inputReducer.ts +66 -11
- package/src/stdio/agentBridge.ts +188 -17
- package/src/stdio/daemonServer.ts +212 -0
- package/src/stdio/index.ts +2 -0
- package/src/stdio/jsonRpcConnection.ts +160 -0
- package/src/stdio/protocol.ts +5 -2
- package/src/stdio/stdioServer.ts +14 -120
- package/src/utils/bracketedPaste.ts +170 -0
- package/src/utils/markdownTable.ts +359 -0
- package/src/utils/throttle.ts +8 -8
package/src/contexts/useChat.tsx
CHANGED
|
@@ -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: (
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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
|
-
|
|
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(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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(
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
+
}
|