wave-code 1.0.0 → 1.0.2
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 +25 -8
- package/dist/components/InputBox.d.ts +1 -3
- package/dist/components/InputBox.js +12 -9
- package/dist/components/LoadingIndicator.d.ts +1 -2
- package/dist/components/LoadingIndicator.js +2 -2
- package/dist/components/LoginCommand.js +4 -2
- package/dist/components/Markdown.js +13 -16
- package/dist/components/Notifications.d.ts +7 -0
- package/dist/components/Notifications.js +9 -0
- package/dist/components/StatusLine.d.ts +0 -4
- package/dist/components/StatusLine.js +6 -10
- 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 +4 -6
- package/dist/contexts/useChat.js +253 -110
- 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 +134 -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 +30 -15
- package/src/components/InputBox.tsx +25 -24
- package/src/components/LoadingIndicator.tsx +1 -4
- package/src/components/LoginCommand.tsx +4 -2
- package/src/components/Markdown.tsx +15 -18
- package/src/components/Notifications.tsx +31 -0
- package/src/components/StatusLine.tsx +17 -44
- 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 +326 -140
- 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 +196 -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/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
|
|
@@ -93,32 +147,27 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
93
147
|
const [currentConfirmation, setCurrentConfirmation] = useState(null);
|
|
94
148
|
// Remount state
|
|
95
149
|
const [remountKey, setRemountKey] = useState(0);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
150
|
+
// Full terminal clear + remount so Ink's append-only <Static> re-renders.
|
|
151
|
+
// Used on structural actions (/clear, /compact, rewind, ctrl-o, forceStatic
|
|
152
|
+
// exit) where stale Static output must not linger on screen.
|
|
153
|
+
const forceRemount = useCallback(() => {
|
|
99
154
|
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", () => {
|
|
100
155
|
setRemountKey((prev) => prev + 1);
|
|
101
156
|
});
|
|
102
|
-
},
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
return () => {
|
|
105
|
-
requestRemount.cancel();
|
|
106
|
-
};
|
|
107
|
-
}, [requestRemount]);
|
|
108
|
-
// Track sessionId changes to trigger remount
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
if (prevSessionId.current &&
|
|
111
|
-
sessionId &&
|
|
112
|
-
prevSessionId.current !== sessionId) {
|
|
113
|
-
requestRemount();
|
|
114
|
-
}
|
|
115
|
-
if (sessionId) {
|
|
116
|
-
prevSessionId.current = sessionId;
|
|
117
|
-
}
|
|
118
|
-
}, [sessionId, requestRemount]);
|
|
157
|
+
}, [stdout]);
|
|
119
158
|
// Status metadata state
|
|
120
159
|
const [workingDirectory, setWorkingDirectory] = useState("");
|
|
121
160
|
const agentRef = useRef(null);
|
|
161
|
+
// Full-list refresh — one-shot pull from the agent, used only for structural
|
|
162
|
+
// changes (compact/clear/rewind/collapse/init). Streaming updates flow through
|
|
163
|
+
// the incremental callbacks in initializeAgent below.
|
|
164
|
+
const refreshMessages = useCallback(() => {
|
|
165
|
+
if (!isExpandedRef.current && agentRef.current) {
|
|
166
|
+
const msgs = [...agentRef.current.messages];
|
|
167
|
+
setMessages(msgs);
|
|
168
|
+
setLatestTotalTokens(extractLatestTotalTokens(msgs));
|
|
169
|
+
}
|
|
170
|
+
}, []);
|
|
122
171
|
// Permission confirmation methods with queue support
|
|
123
172
|
const showConfirmation = useCallback(async (toolName, toolInput, suggestedPrefix, hidePersistentOption, planContent) => {
|
|
124
173
|
return new Promise((resolve, reject) => {
|
|
@@ -139,8 +188,123 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
139
188
|
const initializeAgent = useCallback(async (restoreSessionIdOverride) => {
|
|
140
189
|
const effectiveRestoreSessionId = restoreSessionIdOverride ?? restoreSessionId;
|
|
141
190
|
const callbacks = {
|
|
142
|
-
|
|
143
|
-
|
|
191
|
+
// ── Incremental message updates (no full-list pushes) ──────
|
|
192
|
+
onUserMessageAdded: () => {
|
|
193
|
+
if (isExpandedRef.current || !agentRef.current)
|
|
194
|
+
return;
|
|
195
|
+
const msgs = agentRef.current.messages;
|
|
196
|
+
const last = msgs[msgs.length - 1];
|
|
197
|
+
if (!last || last.role !== "user")
|
|
198
|
+
return;
|
|
199
|
+
setMessages((prev) => prev.some((m) => m.id === last.id) ? prev : [...prev, last]);
|
|
200
|
+
},
|
|
201
|
+
onAssistantMessageAdded: (messageId) => {
|
|
202
|
+
if (isExpandedRef.current || !agentRef.current)
|
|
203
|
+
return;
|
|
204
|
+
const msg = agentRef.current.messages.find((m) => m.id === messageId);
|
|
205
|
+
if (!msg)
|
|
206
|
+
return;
|
|
207
|
+
setMessages((prev) => prev.some((m) => m.id === messageId) ? prev : [...prev, msg]);
|
|
208
|
+
},
|
|
209
|
+
onAssistantContentUpdated: (params) => {
|
|
210
|
+
if (isExpandedRef.current)
|
|
211
|
+
return;
|
|
212
|
+
throttledContentUpdate(params);
|
|
213
|
+
if (params.stage === "end")
|
|
214
|
+
throttledContentUpdate.flush();
|
|
215
|
+
},
|
|
216
|
+
onAssistantReasoningUpdated: (params) => {
|
|
217
|
+
if (isExpandedRef.current)
|
|
218
|
+
return;
|
|
219
|
+
throttledReasoningUpdate(params);
|
|
220
|
+
if (params.stage === "end")
|
|
221
|
+
throttledReasoningUpdate.flush();
|
|
222
|
+
},
|
|
223
|
+
onToolBlockUpdated: (params) => {
|
|
224
|
+
if (isExpandedRef.current)
|
|
225
|
+
return;
|
|
226
|
+
throttledToolBlockUpdate(params);
|
|
227
|
+
if (params.stage === "end")
|
|
228
|
+
throttledToolBlockUpdate.flush();
|
|
229
|
+
},
|
|
230
|
+
onErrorBlockAdded: (error) => {
|
|
231
|
+
if (isExpandedRef.current)
|
|
232
|
+
return;
|
|
233
|
+
setMessages((prev) => {
|
|
234
|
+
// Append to the last assistant message, or create one if none exists
|
|
235
|
+
for (let i = prev.length - 1; i >= 0; i--) {
|
|
236
|
+
if (prev[i].role === "assistant") {
|
|
237
|
+
return prev.map((m, idx) => idx === i
|
|
238
|
+
? {
|
|
239
|
+
...m,
|
|
240
|
+
blocks: [
|
|
241
|
+
...m.blocks,
|
|
242
|
+
{ type: "error", content: error },
|
|
243
|
+
],
|
|
244
|
+
}
|
|
245
|
+
: m);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return [
|
|
249
|
+
...prev,
|
|
250
|
+
{
|
|
251
|
+
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
252
|
+
role: "assistant",
|
|
253
|
+
timestamp: new Date().toISOString(),
|
|
254
|
+
blocks: [{ type: "error", content: error }],
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
onAddBangMessage: (command, messageId) => {
|
|
260
|
+
if (isExpandedRef.current)
|
|
261
|
+
return;
|
|
262
|
+
setMessages((prev) => prev.some((m) => m.id === messageId)
|
|
263
|
+
? prev
|
|
264
|
+
: [
|
|
265
|
+
...prev,
|
|
266
|
+
{
|
|
267
|
+
id: messageId,
|
|
268
|
+
role: "user",
|
|
269
|
+
timestamp: new Date().toISOString(),
|
|
270
|
+
blocks: [
|
|
271
|
+
{
|
|
272
|
+
type: "bang",
|
|
273
|
+
command,
|
|
274
|
+
output: "",
|
|
275
|
+
stage: "running",
|
|
276
|
+
exitCode: null,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
},
|
|
282
|
+
onUpdateBangMessage: (command, output, messageId) => {
|
|
283
|
+
if (isExpandedRef.current)
|
|
284
|
+
return;
|
|
285
|
+
setMessages((prev) => prev.map((m) => m.id === messageId
|
|
286
|
+
? {
|
|
287
|
+
...m,
|
|
288
|
+
blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
|
|
289
|
+
? { ...b, command, output }
|
|
290
|
+
: b),
|
|
291
|
+
}
|
|
292
|
+
: m));
|
|
293
|
+
},
|
|
294
|
+
onCompleteBangMessage: (command, exitCode, messageId) => {
|
|
295
|
+
if (isExpandedRef.current)
|
|
296
|
+
return;
|
|
297
|
+
setMessages((prev) => prev.map((m) => m.id === messageId
|
|
298
|
+
? {
|
|
299
|
+
...m,
|
|
300
|
+
blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
|
|
301
|
+
? { ...b, command, exitCode, stage: "end" }
|
|
302
|
+
: b),
|
|
303
|
+
}
|
|
304
|
+
: m));
|
|
305
|
+
},
|
|
306
|
+
onLatestTotalTokensChange: (tokens) => {
|
|
307
|
+
setLatestTotalTokens(tokens);
|
|
144
308
|
},
|
|
145
309
|
onMcpServersChange: (servers) => {
|
|
146
310
|
setMcpServerStatuses([...servers]);
|
|
@@ -185,19 +349,6 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
185
349
|
onQueuedMessagesChange: (messages) => {
|
|
186
350
|
setQueuedMessages([...messages]);
|
|
187
351
|
},
|
|
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
352
|
};
|
|
202
353
|
try {
|
|
203
354
|
// Create the permission callback inside the try block to access showConfirmation
|
|
@@ -283,7 +434,10 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
283
434
|
originalCwd,
|
|
284
435
|
model,
|
|
285
436
|
initialPermissionMode,
|
|
286
|
-
|
|
437
|
+
refreshMessages,
|
|
438
|
+
throttledContentUpdate,
|
|
439
|
+
throttledReasoningUpdate,
|
|
440
|
+
throttledToolBlockUpdate,
|
|
287
441
|
mcpServers,
|
|
288
442
|
]);
|
|
289
443
|
// Recreate agent (e.g. after plugin install) — destroys current agent and reinitializes
|
|
@@ -318,6 +472,9 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
318
472
|
// Cleanup on unmount
|
|
319
473
|
useEffect(() => {
|
|
320
474
|
return () => {
|
|
475
|
+
throttledContentUpdate.cancel();
|
|
476
|
+
throttledReasoningUpdate.cancel();
|
|
477
|
+
throttledToolBlockUpdate.cancel();
|
|
321
478
|
if (agentRef.current) {
|
|
322
479
|
try {
|
|
323
480
|
// Display usage summary before cleanup
|
|
@@ -331,7 +488,11 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
331
488
|
agentRef.current.destroy();
|
|
332
489
|
}
|
|
333
490
|
};
|
|
334
|
-
}, [
|
|
491
|
+
}, [
|
|
492
|
+
throttledContentUpdate,
|
|
493
|
+
throttledReasoningUpdate,
|
|
494
|
+
throttledToolBlockUpdate,
|
|
495
|
+
]);
|
|
335
496
|
// Trigger WorktreeRemove hook BEFORE agent destruction
|
|
336
497
|
const triggerWorktreeRemoveHook = useCallback(async (worktreePath) => {
|
|
337
498
|
await agentRef.current?.triggerWorktreeRemoveHook(worktreePath);
|
|
@@ -368,30 +529,22 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
368
529
|
console.error("Failed to send message:", error);
|
|
369
530
|
}
|
|
370
531
|
}, []);
|
|
371
|
-
const askBtw = useCallback(async (question) => {
|
|
532
|
+
const askBtw = useCallback(async (question, abortSignal, onContent) => {
|
|
372
533
|
if (!agentRef.current) {
|
|
373
534
|
throw new Error("Agent not initialized");
|
|
374
535
|
}
|
|
375
|
-
return await agentRef.current.askBtw(question);
|
|
536
|
+
return await agentRef.current.askBtw(question, abortSignal, onContent);
|
|
376
537
|
}, []);
|
|
377
538
|
const clearMessages = useCallback(async () => {
|
|
378
539
|
await agentRef.current?.clearMessages();
|
|
379
|
-
|
|
540
|
+
refreshMessages();
|
|
541
|
+
forceRemount();
|
|
542
|
+
}, [refreshMessages, forceRemount]);
|
|
380
543
|
const compact = useCallback(async (instructions) => {
|
|
381
544
|
await agentRef.current?.compact(instructions);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
}, []);
|
|
545
|
+
refreshMessages();
|
|
546
|
+
forceRemount();
|
|
547
|
+
}, [refreshMessages, forceRemount]);
|
|
395
548
|
// Unified interrupt method, interrupt both AI messages and command execution
|
|
396
549
|
const abortMessage = useCallback(() => {
|
|
397
550
|
agentRef.current?.abortMessage();
|
|
@@ -484,13 +637,14 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
484
637
|
if (agentRef.current) {
|
|
485
638
|
try {
|
|
486
639
|
await agentRef.current.truncateHistory(index);
|
|
487
|
-
|
|
640
|
+
refreshMessages();
|
|
641
|
+
forceRemount();
|
|
488
642
|
}
|
|
489
643
|
catch (error) {
|
|
490
644
|
logger.error("Failed to rewind:", error);
|
|
491
645
|
}
|
|
492
646
|
}
|
|
493
|
-
}, [
|
|
647
|
+
}, [forceRemount, refreshMessages]);
|
|
494
648
|
const getFullMessageThread = useCallback(async () => {
|
|
495
649
|
if (agentRef.current) {
|
|
496
650
|
return await agentRef.current.getFullMessageThread();
|
|
@@ -530,23 +684,14 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
530
684
|
isExpandedRef.current = nextExpanded;
|
|
531
685
|
if (nextExpanded) {
|
|
532
686
|
// Transitioning to EXPANDED: Freeze the current view
|
|
533
|
-
//
|
|
534
|
-
throttledSetMessages.cancel();
|
|
687
|
+
// Incremental updates are skipped while expanded (isExpandedRef guard)
|
|
535
688
|
}
|
|
536
689
|
else {
|
|
537
690
|
// Transitioning to COLLAPSED: Restore from agent's actual state
|
|
538
|
-
|
|
539
|
-
const msgs = [...agentRef.current.messages];
|
|
540
|
-
setMessages(msgs);
|
|
541
|
-
setLatestTotalTokens(extractLatestTotalTokens(msgs));
|
|
542
|
-
}
|
|
691
|
+
refreshMessages();
|
|
543
692
|
}
|
|
544
|
-
// Force remount
|
|
545
|
-
|
|
546
|
-
// a previous remount, leaving the UI stuck without a visual update
|
|
547
|
-
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", () => {
|
|
548
|
-
setRemountKey((prev) => prev + 1);
|
|
549
|
-
});
|
|
693
|
+
// Force remount to ensure Static items re-render
|
|
694
|
+
forceRemount();
|
|
550
695
|
}
|
|
551
696
|
if (key.ctrl && input === "t") {
|
|
552
697
|
setIsTaskListVisible((prev) => !prev);
|
|
@@ -563,13 +708,14 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
563
708
|
isExpanded,
|
|
564
709
|
isTaskListVisible,
|
|
565
710
|
setIsTaskListVisible,
|
|
711
|
+
isBtwActive,
|
|
712
|
+
setIsBtwActive,
|
|
566
713
|
queuedMessages,
|
|
567
714
|
sessionId,
|
|
568
715
|
sendMessage,
|
|
569
716
|
askBtw,
|
|
570
717
|
clearMessages,
|
|
571
718
|
compact,
|
|
572
|
-
goalCommand,
|
|
573
719
|
abortMessage,
|
|
574
720
|
recallQueuedMessage,
|
|
575
721
|
removeQueuedMessageById,
|
|
@@ -602,7 +748,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
602
748
|
handleConfirmationCancel,
|
|
603
749
|
backgroundCurrentTask,
|
|
604
750
|
remountKey,
|
|
605
|
-
|
|
751
|
+
forceRemount,
|
|
606
752
|
handleRewindSelect,
|
|
607
753
|
getFullMessageThread,
|
|
608
754
|
getGatewayConfig,
|
|
@@ -612,9 +758,6 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
612
758
|
workdir,
|
|
613
759
|
recreateAgent,
|
|
614
760
|
triggerWorktreeRemoveHook,
|
|
615
|
-
isGoalActive,
|
|
616
|
-
goalElapsed,
|
|
617
|
-
isGoalEvaluating,
|
|
618
761
|
};
|
|
619
762
|
return (_jsx(ChatContext.Provider, { value: contextValue, children: children }));
|
|
620
763
|
};
|
|
@@ -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
|
+
}
|