wave-code 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/wave-code.js +8 -2
- package/dist/bundle/wave.mjs +3207 -0
- package/package.json +24 -25
- package/src/commands/update.ts +2 -7
- package/src/components/App.tsx +3 -7
- package/src/components/BtwDisplay.tsx +10 -1
- package/src/components/ChatInterface.tsx +21 -52
- package/src/components/ConfirmationDetails.tsx +144 -37
- package/src/components/DiffDisplay.tsx +335 -301
- package/src/components/InputBox.tsx +3 -1
- package/src/components/LoadingIndicator.tsx +16 -2
- package/src/components/MessageBlockItem.tsx +2 -4
- package/src/components/MessageList.tsx +75 -14
- package/src/components/ReasoningDisplay.tsx +2 -4
- package/src/components/SessionSelector.tsx +82 -11
- package/src/components/StatusCommand.tsx +2 -9
- package/src/components/ToolDisplay.tsx +2 -4
- package/src/contexts/useChat.tsx +172 -350
- package/src/daemon/commands.ts +57 -27
- package/src/daemon/jsonRpcClient.ts +1 -1
- package/src/daemon/socketClient.ts +2 -2
- package/src/hooks/useInputManager.ts +1 -1
- package/src/index.ts +57 -10
- package/src/managers/inputReducer.ts +186 -104
- package/src/print-cli.ts +21 -17
- package/src/reducers/selectorReducer.ts +10 -0
- package/src/session-selector-cli.tsx +269 -28
- package/src/stdio/agentBridge.ts +22 -21
- package/src/stdio/daemonServer.ts +3 -1
- package/src/stdio/jsonRpcConnection.ts +19 -1
- package/src/stdio/stdioServer.ts +17 -0
- package/src/utils/clipboard.ts +21 -0
- package/src/utils/highlightUtils.ts +46 -1
- package/src/utils/readPackageJson.ts +29 -0
- package/src/utils/streamingText.ts +11 -0
- package/src/utils/worktree.ts +191 -43
- package/dist/cli.d.ts +0 -6
- package/dist/cli.js +0 -53
- package/dist/commands/plugin/disable.d.ts +0 -5
- package/dist/commands/plugin/disable.js +0 -15
- package/dist/commands/plugin/enable.d.ts +0 -5
- package/dist/commands/plugin/enable.js +0 -15
- package/dist/commands/plugin/install.d.ts +0 -5
- package/dist/commands/plugin/install.js +0 -21
- package/dist/commands/plugin/list.d.ts +0 -1
- package/dist/commands/plugin/list.js +0 -31
- package/dist/commands/plugin/marketplace.d.ts +0 -10
- package/dist/commands/plugin/marketplace.js +0 -90
- package/dist/commands/plugin/uninstall.d.ts +0 -3
- package/dist/commands/plugin/uninstall.js +0 -16
- package/dist/commands/plugin/update.d.ts +0 -3
- package/dist/commands/plugin/update.js +0 -15
- package/dist/commands/update.d.ts +0 -1
- package/dist/commands/update.js +0 -97
- package/dist/components/AgentsManager.d.ts +0 -7
- package/dist/components/AgentsManager.js +0 -109
- package/dist/components/App.d.ts +0 -9
- package/dist/components/App.js +0 -76
- package/dist/components/BackgroundTaskManager.d.ts +0 -5
- package/dist/components/BackgroundTaskManager.js +0 -99
- package/dist/components/BangDisplay.d.ts +0 -8
- package/dist/components/BangDisplay.js +0 -17
- package/dist/components/BtwDisplay.d.ts +0 -7
- package/dist/components/BtwDisplay.js +0 -19
- package/dist/components/ChatInterface.d.ts +0 -2
- package/dist/components/ChatInterface.js +0 -61
- package/dist/components/CommandSelector.d.ts +0 -10
- package/dist/components/CommandSelector.js +0 -66
- package/dist/components/CompactDisplay.d.ts +0 -8
- package/dist/components/CompactDisplay.js +0 -13
- package/dist/components/ConfirmationDetails.d.ts +0 -9
- package/dist/components/ConfirmationDetails.js +0 -45
- package/dist/components/ConfirmationSelector.d.ts +0 -13
- package/dist/components/ConfirmationSelector.js +0 -127
- package/dist/components/DiffDisplay.d.ts +0 -8
- package/dist/components/DiffDisplay.js +0 -201
- package/dist/components/DiscoverView.d.ts +0 -2
- package/dist/components/DiscoverView.js +0 -37
- package/dist/components/FileSelector.d.ts +0 -11
- package/dist/components/FileSelector.js +0 -50
- package/dist/components/HelpView.d.ts +0 -7
- package/dist/components/HelpView.js +0 -74
- package/dist/components/HistorySearch.d.ts +0 -8
- package/dist/components/HistorySearch.js +0 -66
- package/dist/components/InputBox.d.ts +0 -24
- package/dist/components/InputBox.js +0 -149
- package/dist/components/InstalledView.d.ts +0 -2
- package/dist/components/InstalledView.js +0 -45
- package/dist/components/LoadingIndicator.d.ts +0 -10
- package/dist/components/LoadingIndicator.js +0 -6
- package/dist/components/LoginCommand.d.ts +0 -5
- package/dist/components/LoginCommand.js +0 -153
- package/dist/components/Markdown.d.ts +0 -5
- package/dist/components/Markdown.js +0 -127
- package/dist/components/MarketplaceAddForm.d.ts +0 -2
- package/dist/components/MarketplaceAddForm.js +0 -55
- package/dist/components/MarketplaceDetail.d.ts +0 -2
- package/dist/components/MarketplaceDetail.js +0 -74
- package/dist/components/MarketplaceList.d.ts +0 -8
- package/dist/components/MarketplaceList.js +0 -17
- package/dist/components/MarketplaceView.d.ts +0 -2
- package/dist/components/MarketplaceView.js +0 -41
- package/dist/components/McpManager.d.ts +0 -9
- package/dist/components/McpManager.js +0 -88
- package/dist/components/MessageBlockItem.d.ts +0 -8
- package/dist/components/MessageBlockItem.js +0 -15
- package/dist/components/MessageList.d.ts +0 -10
- package/dist/components/MessageList.js +0 -80
- package/dist/components/ModelSelector.d.ts +0 -8
- package/dist/components/ModelSelector.js +0 -45
- package/dist/components/Notifications.d.ts +0 -7
- package/dist/components/Notifications.js +0 -9
- package/dist/components/PlanDisplay.d.ts +0 -7
- package/dist/components/PlanDisplay.js +0 -6
- package/dist/components/PluginDetail.d.ts +0 -2
- package/dist/components/PluginDetail.js +0 -87
- package/dist/components/PluginList.d.ts +0 -13
- package/dist/components/PluginList.js +0 -15
- package/dist/components/PluginManagerShell.d.ts +0 -6
- package/dist/components/PluginManagerShell.js +0 -92
- package/dist/components/PluginManagerTypes.d.ts +0 -35
- package/dist/components/PluginManagerTypes.js +0 -1
- package/dist/components/QueuedMessageList.d.ts +0 -2
- package/dist/components/QueuedMessageList.js +0 -18
- package/dist/components/ReasoningDisplay.d.ts +0 -8
- package/dist/components/ReasoningDisplay.js +0 -13
- package/dist/components/RewindCommand.d.ts +0 -12
- package/dist/components/RewindCommand.js +0 -78
- package/dist/components/SessionSelector.d.ts +0 -12
- package/dist/components/SessionSelector.js +0 -47
- package/dist/components/StatusCommand.d.ts +0 -5
- package/dist/components/StatusCommand.js +0 -28
- package/dist/components/StatusLine.d.ts +0 -6
- package/dist/components/StatusLine.js +0 -9
- package/dist/components/TaskList.d.ts +0 -5
- package/dist/components/TaskList.js +0 -197
- package/dist/components/TaskNotificationMessage.d.ts +0 -5
- package/dist/components/TaskNotificationMessage.js +0 -12
- package/dist/components/ToolDisplay.d.ts +0 -9
- package/dist/components/ToolDisplay.js +0 -55
- package/dist/components/WorkflowManager.d.ts +0 -5
- package/dist/components/WorkflowManager.js +0 -102
- package/dist/components/WorktreeExitPrompt.d.ts +0 -12
- package/dist/components/WorktreeExitPrompt.js +0 -35
- package/dist/constants/commands.d.ts +0 -2
- package/dist/constants/commands.js +0 -92
- package/dist/contexts/PluginManagerContext.d.ts +0 -3
- package/dist/contexts/PluginManagerContext.js +0 -9
- package/dist/contexts/useAppConfig.d.ts +0 -10
- package/dist/contexts/useAppConfig.js +0 -13
- package/dist/contexts/useChat.d.ts +0 -101
- package/dist/contexts/useChat.js +0 -992
- package/dist/contracts/status.d.ts +0 -7
- package/dist/contracts/status.js +0 -1
- package/dist/daemon/commands.d.ts +0 -49
- package/dist/daemon/commands.js +0 -341
- package/dist/daemon/jsonRpcClient.d.ts +0 -38
- package/dist/daemon/jsonRpcClient.js +0 -129
- package/dist/daemon/socketClient.d.ts +0 -13
- package/dist/daemon/socketClient.js +0 -26
- package/dist/daemon-cli.d.ts +0 -10
- package/dist/daemon-cli.js +0 -15
- package/dist/hooks/useInputManager.d.ts +0 -75
- package/dist/hooks/useInputManager.js +0 -583
- package/dist/hooks/usePluginManager.d.ts +0 -4
- package/dist/hooks/usePluginManager.js +0 -265
- package/dist/hooks/useTasks.d.ts +0 -1
- package/dist/hooks/useTasks.js +0 -5
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -447
- package/dist/managers/inputHandlers.d.ts +0 -30
- package/dist/managers/inputHandlers.js +0 -625
- package/dist/managers/inputReducer.d.ts +0 -271
- package/dist/managers/inputReducer.js +0 -1121
- package/dist/plugin-manager-cli.d.ts +0 -5
- package/dist/plugin-manager-cli.js +0 -12
- package/dist/print-cli.d.ts +0 -10
- package/dist/print-cli.js +0 -209
- package/dist/reducers/agentsManagerReducer.d.ts +0 -26
- package/dist/reducers/agentsManagerReducer.js +0 -54
- package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
- package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
- package/dist/reducers/confirmationReducer.d.ts +0 -37
- package/dist/reducers/confirmationReducer.js +0 -193
- package/dist/reducers/helpSelectorReducer.d.ts +0 -25
- package/dist/reducers/helpSelectorReducer.js +0 -52
- package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
- package/dist/reducers/marketplaceAddFormReducer.js +0 -58
- package/dist/reducers/mcpManagerReducer.d.ts +0 -37
- package/dist/reducers/mcpManagerReducer.js +0 -74
- package/dist/reducers/pluginDetailReducer.d.ts +0 -32
- package/dist/reducers/pluginDetailReducer.js +0 -70
- package/dist/reducers/questionReducer.d.ts +0 -82
- package/dist/reducers/questionReducer.js +0 -375
- package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
- package/dist/reducers/rewindSelectorReducer.js +0 -37
- package/dist/reducers/selectorReducer.d.ts +0 -23
- package/dist/reducers/selectorReducer.js +0 -42
- package/dist/reducers/workflowManagerReducer.d.ts +0 -40
- package/dist/reducers/workflowManagerReducer.js +0 -100
- package/dist/session-selector-cli.d.ts +0 -3
- package/dist/session-selector-cli.js +0 -25
- package/dist/stdio/agentBridge.d.ts +0 -139
- package/dist/stdio/agentBridge.js +0 -999
- package/dist/stdio/daemonServer.d.ts +0 -67
- package/dist/stdio/daemonServer.js +0 -191
- package/dist/stdio/index.d.ts +0 -5
- package/dist/stdio/index.js +0 -5
- package/dist/stdio/jsonRpcConnection.d.ts +0 -30
- package/dist/stdio/jsonRpcConnection.js +0 -127
- package/dist/stdio/protocol.d.ts +0 -41
- package/dist/stdio/protocol.js +0 -26
- package/dist/stdio/stdioServer.d.ts +0 -28
- package/dist/stdio/stdioServer.js +0 -36
- package/dist/stdio-cli.d.ts +0 -8
- package/dist/stdio-cli.js +0 -15
- package/dist/types.d.ts +0 -20
- package/dist/types.js +0 -1
- package/dist/utils/bracketedPaste.d.ts +0 -39
- package/dist/utils/bracketedPaste.js +0 -122
- package/dist/utils/clipboard.d.ts +0 -21
- package/dist/utils/clipboard.js +0 -347
- package/dist/utils/constants.d.ts +0 -16
- package/dist/utils/constants.js +0 -18
- package/dist/utils/highlightUtils.d.ts +0 -1
- package/dist/utils/highlightUtils.js +0 -93
- package/dist/utils/inputUtils.d.ts +0 -17
- package/dist/utils/inputUtils.js +0 -67
- package/dist/utils/logger.d.ts +0 -71
- package/dist/utils/logger.js +0 -259
- package/dist/utils/markdownTable.d.ts +0 -34
- package/dist/utils/markdownTable.js +0 -302
- package/dist/utils/rewindCheckpoints.d.ts +0 -8
- package/dist/utils/rewindCheckpoints.js +0 -17
- package/dist/utils/throttle.d.ts +0 -14
- package/dist/utils/throttle.js +0 -55
- package/dist/utils/toolParameterTransforms.d.ts +0 -18
- package/dist/utils/toolParameterTransforms.js +0 -73
- package/dist/utils/usageSummary.d.ts +0 -28
- package/dist/utils/usageSummary.js +0 -121
- package/dist/utils/version.d.ts +0 -1
- package/dist/utils/version.js +0 -4
- package/dist/utils/worktree.d.ts +0 -35
- package/dist/utils/worktree.js +0 -219
package/src/contexts/useChat.tsx
CHANGED
|
@@ -5,7 +5,6 @@ import React, {
|
|
|
5
5
|
useRef,
|
|
6
6
|
useEffect,
|
|
7
7
|
useState,
|
|
8
|
-
useMemo,
|
|
9
8
|
} from "react";
|
|
10
9
|
import { useInput, useStdout } from "ink";
|
|
11
10
|
import { useAppConfig } from "./useAppConfig.js";
|
|
@@ -42,6 +41,9 @@ export interface ChatContextType {
|
|
|
42
41
|
isLoading: boolean;
|
|
43
42
|
isCommandRunning: boolean;
|
|
44
43
|
isCompacting: boolean;
|
|
44
|
+
/** Accumulated streaming text from the compaction fork, shown as the
|
|
45
|
+
* last-30-chars tail on the CLI compacting loading indicator. */
|
|
46
|
+
compactionStream: string;
|
|
45
47
|
// Message display state
|
|
46
48
|
isExpanded: boolean;
|
|
47
49
|
isTaskListVisible: boolean;
|
|
@@ -144,8 +146,6 @@ export interface ChatContextType {
|
|
|
144
146
|
workdir?: string;
|
|
145
147
|
// Agent recreation (e.g. after plugin install)
|
|
146
148
|
recreateAgent: () => void;
|
|
147
|
-
// Trigger WorktreeRemove hook BEFORE agent destruction
|
|
148
|
-
triggerWorktreeRemoveHook: (worktreePath: string) => Promise<void>;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
const ChatContext = createContext<ChatContextType | null>(null);
|
|
@@ -178,177 +178,21 @@ interface StreamingUpdateParams {
|
|
|
178
178
|
* reasoning and text content alike. See docs/specs/core/stream-content-updates.md.
|
|
179
179
|
* The clone must be at least one layer deep (message + blocks) so the in-place
|
|
180
180
|
* block mutation never leaks into consumer state.
|
|
181
|
+
*
|
|
182
|
+
* IMPORTANT: the caller must invoke this EAGERLY at callback time and capture
|
|
183
|
+
* the result before scheduling any state update. React batches setState calls
|
|
184
|
+
* that happen in the same synchronous tick and runs the updater functions only
|
|
185
|
+
* at flush time — after the SDK has already written the first chunk into the
|
|
186
|
+
* shared message. A snapshot evaluated inside the updater (lazily) would copy
|
|
187
|
+
* the post-mutation blocks, and the first delta append would then double-count
|
|
188
|
+
* the first word ("HelloHello") whenever `onAssistantMessageAdded` and the
|
|
189
|
+
* first delta callback land in the same batch.
|
|
181
190
|
*/
|
|
182
191
|
const snapshotMessage = (message: Message): Message => ({
|
|
183
192
|
...message,
|
|
184
193
|
blocks: message.blocks.map((block) => ({ ...block })),
|
|
185
194
|
});
|
|
186
195
|
|
|
187
|
-
/**
|
|
188
|
-
* Window-concat throttle for pure-delta streaming updates: chunks arriving
|
|
189
|
-
* within the cooldown window are merged so no delta is lost (a dropped delta
|
|
190
|
-
* would permanently lose content, unlike the accumulated-payload throttle it
|
|
191
|
-
* replaces). Leading edge fires immediately; the trailing edge carries only
|
|
192
|
-
* chunks that arrived within the window. `end` flushes any pending deltas
|
|
193
|
-
* first, then forwards the end signal right away.
|
|
194
|
-
*/
|
|
195
|
-
function createStreamingWindowThrottle(
|
|
196
|
-
fn: (params: StreamingUpdateParams) => void,
|
|
197
|
-
wait: number,
|
|
198
|
-
): {
|
|
199
|
-
(params: StreamingUpdateParams): void;
|
|
200
|
-
cancel: () => void;
|
|
201
|
-
flush: () => void;
|
|
202
|
-
} {
|
|
203
|
-
let timer: NodeJS.Timeout | null = null;
|
|
204
|
-
let pending: { messageId: string; chunk: string } | null = null;
|
|
205
|
-
|
|
206
|
-
const fire = (stage: "streaming" | "end") => {
|
|
207
|
-
if (pending) {
|
|
208
|
-
fn({ ...pending, stage });
|
|
209
|
-
pending = null;
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const throttled = (params: StreamingUpdateParams) => {
|
|
214
|
-
if (params.stage === "end") {
|
|
215
|
-
// Flush any deltas still pending inside the cooldown window first
|
|
216
|
-
if (timer) {
|
|
217
|
-
clearTimeout(timer);
|
|
218
|
-
timer = null;
|
|
219
|
-
}
|
|
220
|
-
fire("streaming");
|
|
221
|
-
fn(params);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
if (pending) {
|
|
225
|
-
pending.chunk += params.chunk;
|
|
226
|
-
} else {
|
|
227
|
-
pending = { messageId: params.messageId, chunk: params.chunk };
|
|
228
|
-
}
|
|
229
|
-
if (!timer) {
|
|
230
|
-
// Leading edge: fire the current delta immediately, then reset pending so
|
|
231
|
-
// the trailing edge only carries chunks arriving within this window
|
|
232
|
-
fire("streaming");
|
|
233
|
-
timer = setTimeout(() => {
|
|
234
|
-
timer = null;
|
|
235
|
-
fire("streaming");
|
|
236
|
-
}, wait);
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
throttled.cancel = () => {
|
|
241
|
-
if (timer) {
|
|
242
|
-
clearTimeout(timer);
|
|
243
|
-
timer = null;
|
|
244
|
-
}
|
|
245
|
-
pending = null;
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
throttled.flush = () => {
|
|
249
|
-
if (timer) {
|
|
250
|
-
clearTimeout(timer);
|
|
251
|
-
timer = null;
|
|
252
|
-
}
|
|
253
|
-
fire("streaming");
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
return throttled;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Per-tool window-concat throttle for pure-delta tool parameter streaming:
|
|
261
|
-
* `parametersChunk` deltas are accumulated independently per tool block id
|
|
262
|
-
* within the cooldown window, so interleaved multi-tool streams lose no delta
|
|
263
|
-
* (a plain throttle's single last-args slot would drop every earlier tool's
|
|
264
|
-
* deltas, leaving the first tool without streaming parameters). `start` /
|
|
265
|
-
* `running` apply immediately (one-shot snapshots); `end` flushes pending
|
|
266
|
-
* streaming deltas first, then applies the authoritative parameters/result.
|
|
267
|
-
*/
|
|
268
|
-
export function createToolStreamingThrottle(
|
|
269
|
-
fn: (params: ToolBlockUpdateCallbackParams) => void,
|
|
270
|
-
wait: number,
|
|
271
|
-
): {
|
|
272
|
-
(params: ToolBlockUpdateCallbackParams): void;
|
|
273
|
-
cancel: () => void;
|
|
274
|
-
flush: () => void;
|
|
275
|
-
} {
|
|
276
|
-
let timer: NodeJS.Timeout | null = null;
|
|
277
|
-
let pending: { messageId: string; chunks: Map<string, string> } | null = null;
|
|
278
|
-
|
|
279
|
-
const fire = () => {
|
|
280
|
-
if (pending && pending.chunks.size > 0) {
|
|
281
|
-
const { messageId, chunks } = pending;
|
|
282
|
-
pending = null;
|
|
283
|
-
for (const [id, chunk] of chunks) {
|
|
284
|
-
fn({ messageId, id, parametersChunk: chunk, stage: "streaming" });
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const throttled = (params: ToolBlockUpdateCallbackParams) => {
|
|
290
|
-
if (params.stage === "end") {
|
|
291
|
-
// Flush any deltas still pending inside the cooldown window first
|
|
292
|
-
if (timer) {
|
|
293
|
-
clearTimeout(timer);
|
|
294
|
-
timer = null;
|
|
295
|
-
}
|
|
296
|
-
fire();
|
|
297
|
-
fn(params);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (params.stage === "streaming") {
|
|
301
|
-
if (!pending) {
|
|
302
|
-
pending = { messageId: params.messageId, chunks: new Map() };
|
|
303
|
-
}
|
|
304
|
-
const prev = pending.chunks.get(params.id) || "";
|
|
305
|
-
pending.chunks.set(params.id, prev + (params.parametersChunk || ""));
|
|
306
|
-
if (!timer) {
|
|
307
|
-
timer = setTimeout(() => {
|
|
308
|
-
timer = null;
|
|
309
|
-
fire();
|
|
310
|
-
}, wait);
|
|
311
|
-
}
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
// start / running — one-shot snapshots applied immediately. Drop this
|
|
315
|
-
// tool's buffered streaming deltas first: start/running carry the
|
|
316
|
-
// authoritative parameters, and a pending timer would otherwise fire late
|
|
317
|
-
// with a stale `streaming` event, regressing this tool block's stage back
|
|
318
|
-
// to streaming (yellow dot -> gray) mid-execution. Other tools' in-flight
|
|
319
|
-
// chunks are kept so interleaved multi-tool streaming still accumulates.
|
|
320
|
-
if (pending) {
|
|
321
|
-
pending.chunks.delete(params.id);
|
|
322
|
-
if (pending.chunks.size === 0) {
|
|
323
|
-
pending = null;
|
|
324
|
-
if (timer) {
|
|
325
|
-
clearTimeout(timer);
|
|
326
|
-
timer = null;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
fn(params);
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
throttled.cancel = () => {
|
|
334
|
-
if (timer) {
|
|
335
|
-
clearTimeout(timer);
|
|
336
|
-
timer = null;
|
|
337
|
-
}
|
|
338
|
-
pending = null;
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
throttled.flush = () => {
|
|
342
|
-
if (timer) {
|
|
343
|
-
clearTimeout(timer);
|
|
344
|
-
timer = null;
|
|
345
|
-
}
|
|
346
|
-
fire();
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
return throttled;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
196
|
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
353
197
|
children,
|
|
354
198
|
bypassPermissions,
|
|
@@ -379,157 +223,138 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
379
223
|
const [latestTotalTokens, setLatestTotalTokens] = useState(0);
|
|
380
224
|
const [maxInputTokens, setMaxInputTokens] = useState(200000);
|
|
381
225
|
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
);
|
|
226
|
+
// Direct incremental streaming updaters — every delta is applied to the
|
|
227
|
+
// message state immediately, without a cooldown window. The former 500ms
|
|
228
|
+
// window-concat throttle is gone: its trailing-edge flush could interleave
|
|
229
|
+
// with a full-list `refreshMessages` snapshot replacement mid-stream and
|
|
230
|
+
// re-append old deltas on top of the authoritative content (first-word
|
|
231
|
+
// duplication). Terminal write frequency is bounded downstream by Ink's
|
|
232
|
+
// built-in ~30fps output throttle. See
|
|
233
|
+
// docs/specs/core/stream-content-updates.md.
|
|
234
|
+
const applyContentUpdate = useCallback((params: StreamingUpdateParams) => {
|
|
235
|
+
const { messageId, chunk, stage } = params;
|
|
236
|
+
setMessages((prev) =>
|
|
237
|
+
prev.map((m) => {
|
|
238
|
+
if (m.id !== messageId) return m;
|
|
239
|
+
const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
|
|
240
|
+
if (textBlockIndex === -1) {
|
|
241
|
+
return {
|
|
242
|
+
...m,
|
|
243
|
+
blocks: [...m.blocks, { type: "text", content: chunk, stage }],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
...m,
|
|
248
|
+
blocks: m.blocks.map((b, idx) =>
|
|
249
|
+
idx === textBlockIndex && b.type === "text"
|
|
250
|
+
? {
|
|
251
|
+
...b,
|
|
252
|
+
content: (b.content || "") + chunk,
|
|
253
|
+
stage,
|
|
254
|
+
}
|
|
255
|
+
: b,
|
|
256
|
+
),
|
|
257
|
+
};
|
|
258
|
+
}),
|
|
259
|
+
);
|
|
260
|
+
}, []);
|
|
418
261
|
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const reasoningBlockIndex = m.blocks.findIndex(
|
|
427
|
-
(b) => b.type === "reasoning",
|
|
428
|
-
);
|
|
429
|
-
if (reasoningBlockIndex === -1) {
|
|
430
|
-
return {
|
|
431
|
-
...m,
|
|
432
|
-
blocks: [
|
|
433
|
-
...m.blocks,
|
|
434
|
-
{ type: "reasoning", content: chunk, stage },
|
|
435
|
-
],
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
return {
|
|
439
|
-
...m,
|
|
440
|
-
blocks: m.blocks.map((b, idx) =>
|
|
441
|
-
idx === reasoningBlockIndex && b.type === "reasoning"
|
|
442
|
-
? {
|
|
443
|
-
...b,
|
|
444
|
-
content: (b.content || "") + chunk,
|
|
445
|
-
stage,
|
|
446
|
-
}
|
|
447
|
-
: b,
|
|
448
|
-
),
|
|
449
|
-
};
|
|
450
|
-
}),
|
|
262
|
+
const applyReasoningUpdate = useCallback((params: StreamingUpdateParams) => {
|
|
263
|
+
const { messageId, chunk, stage } = params;
|
|
264
|
+
setMessages((prev) =>
|
|
265
|
+
prev.map((m) => {
|
|
266
|
+
if (m.id !== messageId) return m;
|
|
267
|
+
const reasoningBlockIndex = m.blocks.findIndex(
|
|
268
|
+
(b) => b.type === "reasoning",
|
|
451
269
|
);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
270
|
+
if (reasoningBlockIndex === -1) {
|
|
271
|
+
return {
|
|
272
|
+
...m,
|
|
273
|
+
blocks: [...m.blocks, { type: "reasoning", content: chunk, stage }],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
...m,
|
|
278
|
+
blocks: m.blocks.map((b, idx) =>
|
|
279
|
+
idx === reasoningBlockIndex && b.type === "reasoning"
|
|
280
|
+
? {
|
|
281
|
+
...b,
|
|
282
|
+
content: (b.content || "") + chunk,
|
|
283
|
+
stage,
|
|
284
|
+
}
|
|
285
|
+
: b,
|
|
286
|
+
),
|
|
287
|
+
};
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
}, []);
|
|
455
291
|
|
|
456
|
-
const
|
|
457
|
-
() =>
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (toolBlockIndex === -1) {
|
|
472
|
-
return {
|
|
473
|
-
...m,
|
|
474
|
-
blocks: [
|
|
475
|
-
...m.blocks,
|
|
476
|
-
{
|
|
477
|
-
type: "tool",
|
|
478
|
-
id: toolBlockId,
|
|
479
|
-
name: updates.name || "",
|
|
480
|
-
stage: updates.stage || "start",
|
|
481
|
-
parameters:
|
|
482
|
-
(updates.parameters || "") + (parametersChunk || ""),
|
|
483
|
-
result: updates.result || "",
|
|
484
|
-
...updates,
|
|
485
|
-
},
|
|
486
|
-
],
|
|
487
|
-
};
|
|
488
|
-
}
|
|
292
|
+
const applyToolBlockUpdate = useCallback(
|
|
293
|
+
(params: ToolBlockUpdateCallbackParams) => {
|
|
294
|
+
const {
|
|
295
|
+
messageId,
|
|
296
|
+
id: toolBlockId,
|
|
297
|
+
parametersChunk,
|
|
298
|
+
...updates
|
|
299
|
+
} = params;
|
|
300
|
+
setMessages((prev) =>
|
|
301
|
+
prev.map((m) => {
|
|
302
|
+
if (m.id !== messageId) return m;
|
|
303
|
+
const toolBlockIndex = m.blocks.findIndex(
|
|
304
|
+
(b) => b.type === "tool" && b.id === toolBlockId,
|
|
305
|
+
);
|
|
306
|
+
if (toolBlockIndex === -1) {
|
|
489
307
|
return {
|
|
490
308
|
...m,
|
|
491
|
-
blocks:
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
: b,
|
|
506
|
-
),
|
|
309
|
+
blocks: [
|
|
310
|
+
...m.blocks,
|
|
311
|
+
{
|
|
312
|
+
type: "tool",
|
|
313
|
+
id: toolBlockId,
|
|
314
|
+
name: updates.name || "",
|
|
315
|
+
stage: updates.stage || "start",
|
|
316
|
+
parameters:
|
|
317
|
+
(updates.parameters || "") + (parametersChunk || ""),
|
|
318
|
+
result: updates.result || "",
|
|
319
|
+
...updates,
|
|
320
|
+
},
|
|
321
|
+
],
|
|
507
322
|
};
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
...m,
|
|
326
|
+
blocks: m.blocks.map((b, idx) =>
|
|
327
|
+
idx === toolBlockIndex && b.type === "tool"
|
|
328
|
+
? {
|
|
329
|
+
...b,
|
|
330
|
+
...updates,
|
|
331
|
+
// Streaming carries only the delta; append it to the
|
|
332
|
+
// accumulated parameters. start/running/end carry the
|
|
333
|
+
// authoritative value and replace wholesale.
|
|
334
|
+
parameters: parametersChunk
|
|
335
|
+
? (b.parameters || "") + parametersChunk
|
|
336
|
+
: updates.parameters !== undefined
|
|
337
|
+
? updates.parameters
|
|
338
|
+
: b.parameters,
|
|
339
|
+
}
|
|
340
|
+
: b,
|
|
341
|
+
),
|
|
342
|
+
};
|
|
343
|
+
}),
|
|
344
|
+
);
|
|
345
|
+
},
|
|
511
346
|
[],
|
|
512
347
|
);
|
|
513
348
|
|
|
514
349
|
useEffect(() => {
|
|
515
350
|
isExpandedRef.current = isExpanded;
|
|
516
|
-
|
|
517
|
-
// Cancel pending throttled updates so the frozen expanded view isn't overwritten
|
|
518
|
-
throttledContentUpdate.cancel();
|
|
519
|
-
throttledReasoningUpdate.cancel();
|
|
520
|
-
throttledToolBlockUpdate.cancel();
|
|
521
|
-
}
|
|
522
|
-
}, [
|
|
523
|
-
isExpanded,
|
|
524
|
-
throttledContentUpdate,
|
|
525
|
-
throttledReasoningUpdate,
|
|
526
|
-
throttledToolBlockUpdate,
|
|
527
|
-
]);
|
|
351
|
+
}, [isExpanded]);
|
|
528
352
|
|
|
529
353
|
const [isLoading, setIsLoading] = useState(false);
|
|
530
354
|
const [sessionId, setSessionId] = useState("");
|
|
531
355
|
const [isCommandRunning, setIsCommandRunning] = useState(false);
|
|
532
356
|
const [isCompacting, setIsCompacting] = useState(false);
|
|
357
|
+
const [compactionStream, setCompactionStream] = useState("");
|
|
533
358
|
const [currentModel, setCurrentModelState] = useState("");
|
|
534
359
|
const [configuredModels, setConfiguredModels] = useState<string[]>([]);
|
|
535
360
|
const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]);
|
|
@@ -603,8 +428,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
603
428
|
const [remountKey, setRemountKey] = useState(0);
|
|
604
429
|
|
|
605
430
|
// Full terminal clear + remount so Ink's append-only <Static> re-renders.
|
|
606
|
-
// Used on structural actions (/clear, /compact, rewind, ctrl-o
|
|
607
|
-
//
|
|
431
|
+
// Used on structural actions (/clear, /compact, rewind, ctrl-o) where stale
|
|
432
|
+
// Static output must not linger on screen.
|
|
608
433
|
const forceRemount = useCallback(() => {
|
|
609
434
|
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", () => {
|
|
610
435
|
setRemountKey((prev) => prev + 1);
|
|
@@ -671,53 +496,57 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
671
496
|
const msgs = agentRef.current.messages;
|
|
672
497
|
const last = msgs[msgs.length - 1];
|
|
673
498
|
if (!last || last.role !== "user") return;
|
|
499
|
+
// Eager snapshot at callback time — React defers updater execution to
|
|
500
|
+
// the batch flush, so evaluating snapshotMessage inside setMessages
|
|
501
|
+
// would read the SDK message after its in-place mutations.
|
|
502
|
+
const snapshot = snapshotMessage(last);
|
|
674
503
|
setMessages((prev) =>
|
|
675
|
-
prev.some((m) => m.id === last.id)
|
|
676
|
-
? prev
|
|
677
|
-
: [...prev, snapshotMessage(last)],
|
|
504
|
+
prev.some((m) => m.id === last.id) ? prev : [...prev, snapshot],
|
|
678
505
|
);
|
|
679
506
|
},
|
|
680
507
|
onAssistantMessageAdded: (messageId: string) => {
|
|
681
508
|
if (isExpandedRef.current || !agentRef.current) return;
|
|
682
509
|
const msg = agentRef.current.messages.find((m) => m.id === messageId);
|
|
683
510
|
if (!msg) return;
|
|
511
|
+
// Eager snapshot (see onUserMessageAdded): `addAssistantMessage()`
|
|
512
|
+
// fires this callback BEFORE the first delta writes into the shared
|
|
513
|
+
// message, so capturing here copies the pre-mutation (empty) blocks.
|
|
514
|
+
const snapshot = snapshotMessage(msg);
|
|
684
515
|
setMessages((prev) =>
|
|
685
|
-
prev.some((m) => m.id === messageId)
|
|
686
|
-
? prev
|
|
687
|
-
: [...prev, snapshotMessage(msg)],
|
|
516
|
+
prev.some((m) => m.id === messageId) ? prev : [...prev, snapshot],
|
|
688
517
|
);
|
|
689
518
|
},
|
|
690
519
|
onAssistantContentUpdated: (params) => {
|
|
691
520
|
if (isExpandedRef.current) return;
|
|
692
|
-
|
|
521
|
+
applyContentUpdate(params);
|
|
693
522
|
},
|
|
694
523
|
onAssistantReasoningUpdated: (params) => {
|
|
695
524
|
if (isExpandedRef.current) return;
|
|
696
|
-
|
|
525
|
+
applyReasoningUpdate(params);
|
|
697
526
|
},
|
|
698
527
|
onToolBlockUpdated: (params) => {
|
|
699
528
|
if (isExpandedRef.current) return;
|
|
700
|
-
|
|
701
|
-
if (params.stage === "end") throttledToolBlockUpdate.flush();
|
|
529
|
+
applyToolBlockUpdate(params);
|
|
702
530
|
},
|
|
703
531
|
onErrorBlockAdded: (error: string) => {
|
|
704
532
|
if (isExpandedRef.current) return;
|
|
705
533
|
setMessages((prev) => {
|
|
706
|
-
// Append to the
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
534
|
+
// Append to the LAST message only if it is an assistant message
|
|
535
|
+
// (the current turn's in-flight reply). If the last message is a
|
|
536
|
+
// user message, the error belongs BELOW it — create a new
|
|
537
|
+
// assistant message instead of polluting the stale assistant from
|
|
538
|
+
// an earlier turn (which surfaced the error above the latest
|
|
539
|
+
// user message and accumulated it there).
|
|
540
|
+
const last = prev[prev.length - 1];
|
|
541
|
+
if (last && last.role === "assistant") {
|
|
542
|
+
return prev.map((m, idx) =>
|
|
543
|
+
idx === prev.length - 1
|
|
544
|
+
? {
|
|
545
|
+
...m,
|
|
546
|
+
blocks: [...m.blocks, { type: "error", content: error }],
|
|
547
|
+
}
|
|
548
|
+
: m,
|
|
549
|
+
);
|
|
721
550
|
}
|
|
722
551
|
return [
|
|
723
552
|
...prev,
|
|
@@ -805,6 +634,14 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
805
634
|
},
|
|
806
635
|
onCompactionStateChange: (isCompactingState) => {
|
|
807
636
|
setIsCompacting(isCompactingState);
|
|
637
|
+
if (!isCompactingState) {
|
|
638
|
+
setCompactionStream("");
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
onCompactionContentUpdate: (content) => {
|
|
642
|
+
// The SDK delivers the accumulated compaction text; the loading
|
|
643
|
+
// indicator renders only its last 30 characters (streaming tail).
|
|
644
|
+
setCompactionStream(content);
|
|
808
645
|
},
|
|
809
646
|
onBackgroundTasksChange: async (tasks) => {
|
|
810
647
|
setBackgroundTasks([...tasks]);
|
|
@@ -954,9 +791,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
954
791
|
originalCwd,
|
|
955
792
|
model,
|
|
956
793
|
initialPermissionMode,
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
794
|
+
applyContentUpdate,
|
|
795
|
+
applyReasoningUpdate,
|
|
796
|
+
applyToolBlockUpdate,
|
|
960
797
|
mcpServers,
|
|
961
798
|
],
|
|
962
799
|
);
|
|
@@ -995,9 +832,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
995
832
|
// Cleanup on unmount
|
|
996
833
|
useEffect(() => {
|
|
997
834
|
return () => {
|
|
998
|
-
throttledContentUpdate.cancel();
|
|
999
|
-
throttledReasoningUpdate.cancel();
|
|
1000
|
-
throttledToolBlockUpdate.cancel();
|
|
1001
835
|
if (agentRef.current) {
|
|
1002
836
|
try {
|
|
1003
837
|
// Display usage summary before cleanup
|
|
@@ -1011,19 +845,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1011
845
|
agentRef.current.destroy();
|
|
1012
846
|
}
|
|
1013
847
|
};
|
|
1014
|
-
}, [
|
|
1015
|
-
throttledContentUpdate,
|
|
1016
|
-
throttledReasoningUpdate,
|
|
1017
|
-
throttledToolBlockUpdate,
|
|
1018
|
-
]);
|
|
1019
|
-
|
|
1020
|
-
// Trigger WorktreeRemove hook BEFORE agent destruction
|
|
1021
|
-
const triggerWorktreeRemoveHook = useCallback(
|
|
1022
|
-
async (worktreePath: string) => {
|
|
1023
|
-
await agentRef.current?.triggerWorktreeRemoveHook(worktreePath);
|
|
1024
|
-
},
|
|
1025
|
-
[],
|
|
1026
|
-
);
|
|
848
|
+
}, []);
|
|
1027
849
|
|
|
1028
850
|
// Send message function (including judgment logic)
|
|
1029
851
|
const sendMessage = useCallback(
|
|
@@ -1353,6 +1175,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1353
1175
|
getConfiguredModels,
|
|
1354
1176
|
setModel,
|
|
1355
1177
|
isCompacting,
|
|
1178
|
+
compactionStream,
|
|
1356
1179
|
mcpServers: mcpServerStatuses,
|
|
1357
1180
|
connectMcpServer,
|
|
1358
1181
|
disconnectMcpServer,
|
|
@@ -1386,7 +1209,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1386
1209
|
version,
|
|
1387
1210
|
workdir,
|
|
1388
1211
|
recreateAgent,
|
|
1389
|
-
triggerWorktreeRemoveHook,
|
|
1390
1212
|
};
|
|
1391
1213
|
|
|
1392
1214
|
return (
|