wave-code 0.19.9 → 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/HelpView.js +6 -0
- package/dist/components/InputBox.d.ts +1 -2
- package/dist/components/InputBox.js +16 -5
- 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/RewindCommand.js +4 -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.d.ts +1 -0
- package/dist/hooks/useInputManager.js +120 -40
- package/dist/index.js +10 -0
- package/dist/managers/inputHandlers.js +55 -30
- package/dist/managers/inputReducer.d.ts +22 -22
- package/dist/managers/inputReducer.js +361 -177
- package/dist/print-cli.js +36 -10
- package/dist/stdio/agentBridge.d.ts +23 -0
- package/dist/stdio/agentBridge.js +151 -18
- 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/rewindCheckpoints.d.ts +8 -0
- package/dist/utils/rewindCheckpoints.js +15 -0
- package/dist/utils/throttle.d.ts +3 -3
- package/dist/utils/worktree.d.ts +8 -0
- package/dist/utils/worktree.js +32 -1
- 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 +26 -11
- package/src/components/HelpView.tsx +6 -0
- package/src/components/InputBox.tsx +21 -10
- package/src/components/LoadingIndicator.tsx +1 -4
- package/src/components/Markdown.tsx +15 -18
- package/src/components/MessageList.tsx +6 -0
- package/src/components/RewindCommand.tsx +4 -2
- 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 +135 -43
- package/src/index.ts +12 -0
- package/src/managers/inputHandlers.ts +55 -32
- package/src/managers/inputReducer.ts +442 -214
- package/src/print-cli.ts +48 -11
- package/src/stdio/agentBridge.ts +213 -18
- 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/rewindCheckpoints.ts +15 -0
- package/src/utils/throttle.ts +8 -8
- package/src/utils/worktree.ts +50 -1
package/dist/contexts/useChat.js
CHANGED
|
@@ -22,27 +22,104 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
22
22
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
23
23
|
const isExpandedRef = useRef(isExpanded);
|
|
24
24
|
const [isTaskListVisible, setIsTaskListVisible] = useState(true);
|
|
25
|
+
const [isBtwActive, setIsBtwActive] = useState(false);
|
|
25
26
|
const [messages, setMessages] = useState([]);
|
|
26
27
|
const [latestTotalTokens, setLatestTotalTokens] = useState(0);
|
|
27
28
|
const [maxInputTokens, setMaxInputTokens] = useState(200000);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
// Throttled incremental streaming updaters — 500ms leading+trailing, the same interval
|
|
30
|
+
// as the pre-incremental throttledSetMessages. `stage === "end"` flushes the final
|
|
31
|
+
// update immediately so completion results are never delayed.
|
|
32
|
+
const throttledContentUpdate = useMemo(() => throttle((params) => {
|
|
33
|
+
const { messageId, accumulated, stage } = params;
|
|
34
|
+
setMessages((prev) => prev.map((m) => {
|
|
35
|
+
if (m.id !== messageId)
|
|
36
|
+
return m;
|
|
37
|
+
const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
|
|
38
|
+
if (textBlockIndex === -1) {
|
|
39
|
+
return {
|
|
40
|
+
...m,
|
|
41
|
+
blocks: [
|
|
42
|
+
...m.blocks,
|
|
43
|
+
{ type: "text", content: accumulated, stage },
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...m,
|
|
49
|
+
blocks: m.blocks.map((b, idx) => idx === textBlockIndex && b.type === "text"
|
|
50
|
+
? { ...b, content: accumulated, stage }
|
|
51
|
+
: b),
|
|
52
|
+
};
|
|
53
|
+
}));
|
|
54
|
+
}, 500), []);
|
|
55
|
+
const throttledReasoningUpdate = useMemo(() => throttle((params) => {
|
|
56
|
+
const { messageId, accumulated, stage } = params;
|
|
57
|
+
setMessages((prev) => prev.map((m) => {
|
|
58
|
+
if (m.id !== messageId)
|
|
59
|
+
return m;
|
|
60
|
+
const reasoningBlockIndex = m.blocks.findIndex((b) => b.type === "reasoning");
|
|
61
|
+
if (reasoningBlockIndex === -1) {
|
|
62
|
+
return {
|
|
63
|
+
...m,
|
|
64
|
+
blocks: [
|
|
65
|
+
...m.blocks,
|
|
66
|
+
{ type: "reasoning", content: accumulated, stage },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
...m,
|
|
72
|
+
blocks: m.blocks.map((b, idx) => idx === reasoningBlockIndex && b.type === "reasoning"
|
|
73
|
+
? { ...b, content: accumulated, stage }
|
|
74
|
+
: b),
|
|
75
|
+
};
|
|
76
|
+
}));
|
|
77
|
+
}, 500), []);
|
|
78
|
+
const throttledToolBlockUpdate = useMemo(() => throttle((params) => {
|
|
79
|
+
const { messageId, id: toolBlockId, ...updates } = params;
|
|
80
|
+
setMessages((prev) => prev.map((m) => {
|
|
81
|
+
if (m.id !== messageId)
|
|
82
|
+
return m;
|
|
83
|
+
const toolBlockIndex = m.blocks.findIndex((b) => b.type === "tool" && b.id === toolBlockId);
|
|
84
|
+
if (toolBlockIndex === -1) {
|
|
85
|
+
return {
|
|
86
|
+
...m,
|
|
87
|
+
blocks: [
|
|
88
|
+
...m.blocks,
|
|
89
|
+
{
|
|
90
|
+
type: "tool",
|
|
91
|
+
id: toolBlockId,
|
|
92
|
+
name: updates.name || "",
|
|
93
|
+
stage: updates.stage || "start",
|
|
94
|
+
parameters: updates.parameters || "",
|
|
95
|
+
result: updates.result || "",
|
|
96
|
+
...updates,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...m,
|
|
103
|
+
blocks: m.blocks.map((b, idx) => idx === toolBlockIndex && b.type === "tool"
|
|
104
|
+
? { ...b, ...updates }
|
|
105
|
+
: b),
|
|
106
|
+
};
|
|
107
|
+
}));
|
|
108
|
+
}, 500), []);
|
|
35
109
|
useEffect(() => {
|
|
36
110
|
isExpandedRef.current = isExpanded;
|
|
37
111
|
if (isExpanded) {
|
|
38
|
-
|
|
112
|
+
// Cancel pending throttled updates so the frozen expanded view isn't overwritten
|
|
113
|
+
throttledContentUpdate.cancel();
|
|
114
|
+
throttledReasoningUpdate.cancel();
|
|
115
|
+
throttledToolBlockUpdate.cancel();
|
|
39
116
|
}
|
|
40
|
-
}, [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
117
|
+
}, [
|
|
118
|
+
isExpanded,
|
|
119
|
+
throttledContentUpdate,
|
|
120
|
+
throttledReasoningUpdate,
|
|
121
|
+
throttledToolBlockUpdate,
|
|
122
|
+
]);
|
|
46
123
|
const [isLoading, setIsLoading] = useState(false);
|
|
47
124
|
const [sessionId, setSessionId] = useState("");
|
|
48
125
|
const [isCommandRunning, setIsCommandRunning] = useState(false);
|
|
@@ -50,29 +127,6 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
50
127
|
const [currentModel, setCurrentModelState] = useState("");
|
|
51
128
|
const [configuredModels, setConfiguredModels] = useState([]);
|
|
52
129
|
const [queuedMessages, setQueuedMessages] = useState([]);
|
|
53
|
-
const [isGoalActive, setIsGoalActive] = useState(false);
|
|
54
|
-
const [goalElapsed, setGoalElapsed] = useState();
|
|
55
|
-
const [isGoalEvaluating, setIsGoalEvaluating] = useState(false);
|
|
56
|
-
const goalStartedAt = useRef(null);
|
|
57
|
-
// Update goal elapsed time every 30s while active
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (!isGoalActive || goalStartedAt.current === null)
|
|
60
|
-
return;
|
|
61
|
-
const formatElapsed = (ms) => {
|
|
62
|
-
const minutes = Math.floor(ms / 60000);
|
|
63
|
-
if (minutes < 1)
|
|
64
|
-
return "<1m";
|
|
65
|
-
if (minutes < 60)
|
|
66
|
-
return `${minutes}m`;
|
|
67
|
-
const hours = Math.floor(minutes / 60);
|
|
68
|
-
const remainingMin = minutes % 60;
|
|
69
|
-
return `${hours}h${remainingMin}m`;
|
|
70
|
-
};
|
|
71
|
-
const update = () => setGoalElapsed(formatElapsed(Date.now() - goalStartedAt.current));
|
|
72
|
-
update();
|
|
73
|
-
const timer = setInterval(update, 30000);
|
|
74
|
-
return () => clearInterval(timer);
|
|
75
|
-
}, [isGoalActive]);
|
|
76
130
|
// MCP State
|
|
77
131
|
const [mcpServerStatuses, setMcpServerStatuses] = useState([]);
|
|
78
132
|
// Background tasks state
|
|
@@ -119,6 +173,16 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
119
173
|
// Status metadata state
|
|
120
174
|
const [workingDirectory, setWorkingDirectory] = useState("");
|
|
121
175
|
const agentRef = useRef(null);
|
|
176
|
+
// Full-list refresh — one-shot pull from the agent, used only for structural
|
|
177
|
+
// changes (compact/clear/rewind/collapse/init). Streaming updates flow through
|
|
178
|
+
// the incremental callbacks in initializeAgent below.
|
|
179
|
+
const refreshMessages = useCallback(() => {
|
|
180
|
+
if (!isExpandedRef.current && agentRef.current) {
|
|
181
|
+
const msgs = [...agentRef.current.messages];
|
|
182
|
+
setMessages(msgs);
|
|
183
|
+
setLatestTotalTokens(extractLatestTotalTokens(msgs));
|
|
184
|
+
}
|
|
185
|
+
}, []);
|
|
122
186
|
// Permission confirmation methods with queue support
|
|
123
187
|
const showConfirmation = useCallback(async (toolName, toolInput, suggestedPrefix, hidePersistentOption, planContent) => {
|
|
124
188
|
return new Promise((resolve, reject) => {
|
|
@@ -139,8 +203,123 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
139
203
|
const initializeAgent = useCallback(async (restoreSessionIdOverride) => {
|
|
140
204
|
const effectiveRestoreSessionId = restoreSessionIdOverride ?? restoreSessionId;
|
|
141
205
|
const callbacks = {
|
|
142
|
-
|
|
143
|
-
|
|
206
|
+
// ── Incremental message updates (no full-list pushes) ──────
|
|
207
|
+
onUserMessageAdded: () => {
|
|
208
|
+
if (isExpandedRef.current || !agentRef.current)
|
|
209
|
+
return;
|
|
210
|
+
const msgs = agentRef.current.messages;
|
|
211
|
+
const last = msgs[msgs.length - 1];
|
|
212
|
+
if (!last || last.role !== "user")
|
|
213
|
+
return;
|
|
214
|
+
setMessages((prev) => prev.some((m) => m.id === last.id) ? prev : [...prev, last]);
|
|
215
|
+
},
|
|
216
|
+
onAssistantMessageAdded: (messageId) => {
|
|
217
|
+
if (isExpandedRef.current || !agentRef.current)
|
|
218
|
+
return;
|
|
219
|
+
const msg = agentRef.current.messages.find((m) => m.id === messageId);
|
|
220
|
+
if (!msg)
|
|
221
|
+
return;
|
|
222
|
+
setMessages((prev) => prev.some((m) => m.id === messageId) ? prev : [...prev, msg]);
|
|
223
|
+
},
|
|
224
|
+
onAssistantContentUpdated: (params) => {
|
|
225
|
+
if (isExpandedRef.current)
|
|
226
|
+
return;
|
|
227
|
+
throttledContentUpdate(params);
|
|
228
|
+
if (params.stage === "end")
|
|
229
|
+
throttledContentUpdate.flush();
|
|
230
|
+
},
|
|
231
|
+
onAssistantReasoningUpdated: (params) => {
|
|
232
|
+
if (isExpandedRef.current)
|
|
233
|
+
return;
|
|
234
|
+
throttledReasoningUpdate(params);
|
|
235
|
+
if (params.stage === "end")
|
|
236
|
+
throttledReasoningUpdate.flush();
|
|
237
|
+
},
|
|
238
|
+
onToolBlockUpdated: (params) => {
|
|
239
|
+
if (isExpandedRef.current)
|
|
240
|
+
return;
|
|
241
|
+
throttledToolBlockUpdate(params);
|
|
242
|
+
if (params.stage === "end")
|
|
243
|
+
throttledToolBlockUpdate.flush();
|
|
244
|
+
},
|
|
245
|
+
onErrorBlockAdded: (error) => {
|
|
246
|
+
if (isExpandedRef.current)
|
|
247
|
+
return;
|
|
248
|
+
setMessages((prev) => {
|
|
249
|
+
// Append to the last assistant message, or create one if none exists
|
|
250
|
+
for (let i = prev.length - 1; i >= 0; i--) {
|
|
251
|
+
if (prev[i].role === "assistant") {
|
|
252
|
+
return prev.map((m, idx) => idx === i
|
|
253
|
+
? {
|
|
254
|
+
...m,
|
|
255
|
+
blocks: [
|
|
256
|
+
...m.blocks,
|
|
257
|
+
{ type: "error", content: error },
|
|
258
|
+
],
|
|
259
|
+
}
|
|
260
|
+
: m);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return [
|
|
264
|
+
...prev,
|
|
265
|
+
{
|
|
266
|
+
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
267
|
+
role: "assistant",
|
|
268
|
+
timestamp: new Date().toISOString(),
|
|
269
|
+
blocks: [{ type: "error", content: error }],
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
onAddBangMessage: (command, messageId) => {
|
|
275
|
+
if (isExpandedRef.current)
|
|
276
|
+
return;
|
|
277
|
+
setMessages((prev) => prev.some((m) => m.id === messageId)
|
|
278
|
+
? prev
|
|
279
|
+
: [
|
|
280
|
+
...prev,
|
|
281
|
+
{
|
|
282
|
+
id: messageId,
|
|
283
|
+
role: "user",
|
|
284
|
+
timestamp: new Date().toISOString(),
|
|
285
|
+
blocks: [
|
|
286
|
+
{
|
|
287
|
+
type: "bang",
|
|
288
|
+
command,
|
|
289
|
+
output: "",
|
|
290
|
+
stage: "running",
|
|
291
|
+
exitCode: null,
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
]);
|
|
296
|
+
},
|
|
297
|
+
onUpdateBangMessage: (command, output, messageId) => {
|
|
298
|
+
if (isExpandedRef.current)
|
|
299
|
+
return;
|
|
300
|
+
setMessages((prev) => prev.map((m) => m.id === messageId
|
|
301
|
+
? {
|
|
302
|
+
...m,
|
|
303
|
+
blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
|
|
304
|
+
? { ...b, command, output }
|
|
305
|
+
: b),
|
|
306
|
+
}
|
|
307
|
+
: m));
|
|
308
|
+
},
|
|
309
|
+
onCompleteBangMessage: (command, exitCode, messageId) => {
|
|
310
|
+
if (isExpandedRef.current)
|
|
311
|
+
return;
|
|
312
|
+
setMessages((prev) => prev.map((m) => m.id === messageId
|
|
313
|
+
? {
|
|
314
|
+
...m,
|
|
315
|
+
blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
|
|
316
|
+
? { ...b, command, exitCode, stage: "end" }
|
|
317
|
+
: b),
|
|
318
|
+
}
|
|
319
|
+
: m));
|
|
320
|
+
},
|
|
321
|
+
onLatestTotalTokensChange: (tokens) => {
|
|
322
|
+
setLatestTotalTokens(tokens);
|
|
144
323
|
},
|
|
145
324
|
onMcpServersChange: (servers) => {
|
|
146
325
|
setMcpServerStatuses([...servers]);
|
|
@@ -185,19 +364,6 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
185
364
|
onQueuedMessagesChange: (messages) => {
|
|
186
365
|
setQueuedMessages([...messages]);
|
|
187
366
|
},
|
|
188
|
-
onGoalStateChange: (active, _condition, elapsed) => {
|
|
189
|
-
setIsGoalActive(active);
|
|
190
|
-
if (active) {
|
|
191
|
-
goalStartedAt.current = Date.now();
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
goalStartedAt.current = null;
|
|
195
|
-
}
|
|
196
|
-
setGoalElapsed(elapsed);
|
|
197
|
-
},
|
|
198
|
-
onGoalEvaluating: (evaluating) => {
|
|
199
|
-
setIsGoalEvaluating(evaluating);
|
|
200
|
-
},
|
|
201
367
|
};
|
|
202
368
|
try {
|
|
203
369
|
// Create the permission callback inside the try block to access showConfirmation
|
|
@@ -283,7 +449,10 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
283
449
|
originalCwd,
|
|
284
450
|
model,
|
|
285
451
|
initialPermissionMode,
|
|
286
|
-
|
|
452
|
+
refreshMessages,
|
|
453
|
+
throttledContentUpdate,
|
|
454
|
+
throttledReasoningUpdate,
|
|
455
|
+
throttledToolBlockUpdate,
|
|
287
456
|
mcpServers,
|
|
288
457
|
]);
|
|
289
458
|
// Recreate agent (e.g. after plugin install) — destroys current agent and reinitializes
|
|
@@ -318,6 +487,9 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
318
487
|
// Cleanup on unmount
|
|
319
488
|
useEffect(() => {
|
|
320
489
|
return () => {
|
|
490
|
+
throttledContentUpdate.cancel();
|
|
491
|
+
throttledReasoningUpdate.cancel();
|
|
492
|
+
throttledToolBlockUpdate.cancel();
|
|
321
493
|
if (agentRef.current) {
|
|
322
494
|
try {
|
|
323
495
|
// Display usage summary before cleanup
|
|
@@ -331,7 +503,11 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
331
503
|
agentRef.current.destroy();
|
|
332
504
|
}
|
|
333
505
|
};
|
|
334
|
-
}, [
|
|
506
|
+
}, [
|
|
507
|
+
throttledContentUpdate,
|
|
508
|
+
throttledReasoningUpdate,
|
|
509
|
+
throttledToolBlockUpdate,
|
|
510
|
+
]);
|
|
335
511
|
// Trigger WorktreeRemove hook BEFORE agent destruction
|
|
336
512
|
const triggerWorktreeRemoveHook = useCallback(async (worktreePath) => {
|
|
337
513
|
await agentRef.current?.triggerWorktreeRemoveHook(worktreePath);
|
|
@@ -368,30 +544,20 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
368
544
|
console.error("Failed to send message:", error);
|
|
369
545
|
}
|
|
370
546
|
}, []);
|
|
371
|
-
const askBtw = useCallback(async (question) => {
|
|
547
|
+
const askBtw = useCallback(async (question, abortSignal, onContent) => {
|
|
372
548
|
if (!agentRef.current) {
|
|
373
549
|
throw new Error("Agent not initialized");
|
|
374
550
|
}
|
|
375
|
-
return await agentRef.current.askBtw(question);
|
|
551
|
+
return await agentRef.current.askBtw(question, abortSignal, onContent);
|
|
376
552
|
}, []);
|
|
377
553
|
const clearMessages = useCallback(async () => {
|
|
378
554
|
await agentRef.current?.clearMessages();
|
|
379
|
-
|
|
555
|
+
refreshMessages();
|
|
556
|
+
}, [refreshMessages]);
|
|
380
557
|
const compact = useCallback(async (instructions) => {
|
|
381
558
|
await agentRef.current?.compact(instructions);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const trimmed = args?.trim() ?? "";
|
|
385
|
-
if (!trimmed) {
|
|
386
|
-
await agentRef.current?.showGoalStatus();
|
|
387
|
-
}
|
|
388
|
-
else if (["clear", "stop", "off", "reset", "none", "cancel"].includes(trimmed)) {
|
|
389
|
-
await agentRef.current?.clearGoal();
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
await agentRef.current?.setGoal(trimmed);
|
|
393
|
-
}
|
|
394
|
-
}, []);
|
|
559
|
+
refreshMessages();
|
|
560
|
+
}, [refreshMessages]);
|
|
395
561
|
// Unified interrupt method, interrupt both AI messages and command execution
|
|
396
562
|
const abortMessage = useCallback(() => {
|
|
397
563
|
agentRef.current?.abortMessage();
|
|
@@ -484,13 +650,14 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
484
650
|
if (agentRef.current) {
|
|
485
651
|
try {
|
|
486
652
|
await agentRef.current.truncateHistory(index);
|
|
653
|
+
refreshMessages();
|
|
487
654
|
requestRemount();
|
|
488
655
|
}
|
|
489
656
|
catch (error) {
|
|
490
657
|
logger.error("Failed to rewind:", error);
|
|
491
658
|
}
|
|
492
659
|
}
|
|
493
|
-
}, [requestRemount]);
|
|
660
|
+
}, [requestRemount, refreshMessages]);
|
|
494
661
|
const getFullMessageThread = useCallback(async () => {
|
|
495
662
|
if (agentRef.current) {
|
|
496
663
|
return await agentRef.current.getFullMessageThread();
|
|
@@ -530,16 +697,11 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
530
697
|
isExpandedRef.current = nextExpanded;
|
|
531
698
|
if (nextExpanded) {
|
|
532
699
|
// Transitioning to EXPANDED: Freeze the current view
|
|
533
|
-
//
|
|
534
|
-
throttledSetMessages.cancel();
|
|
700
|
+
// Incremental updates are skipped while expanded (isExpandedRef guard)
|
|
535
701
|
}
|
|
536
702
|
else {
|
|
537
703
|
// Transitioning to COLLAPSED: Restore from agent's actual state
|
|
538
|
-
|
|
539
|
-
const msgs = [...agentRef.current.messages];
|
|
540
|
-
setMessages(msgs);
|
|
541
|
-
setLatestTotalTokens(extractLatestTotalTokens(msgs));
|
|
542
|
-
}
|
|
704
|
+
refreshMessages();
|
|
543
705
|
}
|
|
544
706
|
// Force remount directly (bypass throttle) to ensure Static items re-render
|
|
545
707
|
// The throttled requestRemount can be dropped if pressed too quickly after
|
|
@@ -563,13 +725,14 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
563
725
|
isExpanded,
|
|
564
726
|
isTaskListVisible,
|
|
565
727
|
setIsTaskListVisible,
|
|
728
|
+
isBtwActive,
|
|
729
|
+
setIsBtwActive,
|
|
566
730
|
queuedMessages,
|
|
567
731
|
sessionId,
|
|
568
732
|
sendMessage,
|
|
569
733
|
askBtw,
|
|
570
734
|
clearMessages,
|
|
571
735
|
compact,
|
|
572
|
-
goalCommand,
|
|
573
736
|
abortMessage,
|
|
574
737
|
recallQueuedMessage,
|
|
575
738
|
removeQueuedMessageById,
|
|
@@ -612,9 +775,6 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
612
775
|
workdir,
|
|
613
776
|
recreateAgent,
|
|
614
777
|
triggerWorktreeRemoveHook,
|
|
615
|
-
isGoalActive,
|
|
616
|
-
goalElapsed,
|
|
617
|
-
isGoalEvaluating,
|
|
618
778
|
};
|
|
619
779
|
return (_jsx(ChatContext.Provider, { value: contextValue, children: children }));
|
|
620
780
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
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>;
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|
|
@@ -26,6 +26,7 @@ export declare const useInputManager: (callbacks?: Partial<InputManagerCallbacks
|
|
|
26
26
|
permissionMode: PermissionMode;
|
|
27
27
|
attachedImages: import("../managers/inputReducer.js").AttachedImage[];
|
|
28
28
|
btwState: import("../managers/inputReducer.js").BtwState;
|
|
29
|
+
escClearPending: boolean;
|
|
29
30
|
isManagerReady: boolean;
|
|
30
31
|
insertTextAtCursor: (text: string) => void;
|
|
31
32
|
deleteCharAtCursor: () => void;
|