wave-code 1.0.10 → 1.1.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/bin/wave-code.js +8 -2
- package/dist/bundle/wave.mjs +3231 -0
- package/package.json +24 -25
- package/src/cli.tsx +6 -1
- 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 +17 -2
- 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/SkillsManager.tsx +312 -0
- package/src/components/StatusCommand.tsx +2 -9
- package/src/components/ToolDisplay.tsx +2 -4
- package/src/constants/commands.ts +6 -0
- package/src/contexts/useChat.tsx +245 -301
- 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 +7 -1
- package/src/index.ts +57 -10
- package/src/managers/inputHandlers.ts +2 -0
- package/src/managers/inputReducer.ts +195 -104
- package/src/print-cli.ts +25 -17
- package/src/reducers/selectorReducer.ts +10 -0
- package/src/reducers/skillsManagerReducer.ts +91 -0
- package/src/session-selector-cli.tsx +269 -28
- package/src/stdio/agentBridge.ts +30 -21
- package/src/stdio/daemonServer.ts +3 -1
- package/src/stdio/protocol.ts +2 -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 -993
- 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 -39
- package/dist/stdio/jsonRpcConnection.js +0 -135
- package/dist/stdio/protocol.d.ts +0 -41
- package/dist/stdio/protocol.js +0 -26
- package/dist/stdio/stdioServer.d.ts +0 -36
- package/dist/stdio/stdioServer.js +0 -51
- 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
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
Task,
|
|
17
17
|
SlashCommand,
|
|
18
18
|
SubagentConfiguration,
|
|
19
|
+
SkillMetadata,
|
|
19
20
|
PermissionDecision,
|
|
20
21
|
PermissionMode,
|
|
21
22
|
QueuedMessage,
|
|
@@ -42,6 +43,9 @@ export interface ChatContextType {
|
|
|
42
43
|
isLoading: boolean;
|
|
43
44
|
isCommandRunning: boolean;
|
|
44
45
|
isCompacting: boolean;
|
|
46
|
+
/** Accumulated streaming text from the compaction fork, shown as the
|
|
47
|
+
* last-30-chars tail on the CLI compacting loading indicator. */
|
|
48
|
+
compactionStream: string;
|
|
45
49
|
// Message display state
|
|
46
50
|
isExpanded: boolean;
|
|
47
51
|
isTaskListVisible: boolean;
|
|
@@ -98,6 +102,8 @@ export interface ChatContextType {
|
|
|
98
102
|
hasSlashCommand: (commandId: string) => boolean;
|
|
99
103
|
// Agent definitions (for /agents overlay)
|
|
100
104
|
agentDefinitions: SubagentConfiguration[];
|
|
105
|
+
// Skill metadata (for /skills overlay)
|
|
106
|
+
skills: SkillMetadata[];
|
|
101
107
|
// Permission functionality
|
|
102
108
|
permissionMode: PermissionMode;
|
|
103
109
|
setPermissionMode: (mode: PermissionMode) => void;
|
|
@@ -144,8 +150,6 @@ export interface ChatContextType {
|
|
|
144
150
|
workdir?: string;
|
|
145
151
|
// Agent recreation (e.g. after plugin install)
|
|
146
152
|
recreateAgent: () => void;
|
|
147
|
-
// Trigger WorktreeRemove hook BEFORE agent destruction
|
|
148
|
-
triggerWorktreeRemoveHook: (worktreePath: string) => Promise<void>;
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
const ChatContext = createContext<ChatContextType | null>(null);
|
|
@@ -178,6 +182,15 @@ interface StreamingUpdateParams {
|
|
|
178
182
|
* reasoning and text content alike. See docs/specs/core/stream-content-updates.md.
|
|
179
183
|
* The clone must be at least one layer deep (message + blocks) so the in-place
|
|
180
184
|
* block mutation never leaks into consumer state.
|
|
185
|
+
*
|
|
186
|
+
* IMPORTANT: the caller must invoke this EAGERLY at callback time and capture
|
|
187
|
+
* the result before scheduling any state update. React batches setState calls
|
|
188
|
+
* that happen in the same synchronous tick and runs the updater functions only
|
|
189
|
+
* at flush time — after the SDK has already written the first chunk into the
|
|
190
|
+
* shared message. A snapshot evaluated inside the updater (lazily) would copy
|
|
191
|
+
* the post-mutation blocks, and the first delta append would then double-count
|
|
192
|
+
* the first word ("HelloHello") whenever `onAssistantMessageAdded` and the
|
|
193
|
+
* first delta callback land in the same batch.
|
|
181
194
|
*/
|
|
182
195
|
const snapshotMessage = (message: Message): Message => ({
|
|
183
196
|
...message,
|
|
@@ -185,149 +198,178 @@ const snapshotMessage = (message: Message): Message => ({
|
|
|
185
198
|
});
|
|
186
199
|
|
|
187
200
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
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.
|
|
201
|
+
* Pure updater: appends a text-content delta to the target message's text
|
|
202
|
+
* block (creating it on first delta), applying the stage signal.
|
|
194
203
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
): {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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);
|
|
204
|
+
const applyContentDelta = (
|
|
205
|
+
prev: Message[],
|
|
206
|
+
params: StreamingUpdateParams,
|
|
207
|
+
): Message[] => {
|
|
208
|
+
const { messageId, chunk, stage } = params;
|
|
209
|
+
return prev.map((m) => {
|
|
210
|
+
if (m.id !== messageId) return m;
|
|
211
|
+
const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
|
|
212
|
+
if (textBlockIndex === -1) {
|
|
213
|
+
return {
|
|
214
|
+
...m,
|
|
215
|
+
blocks: [...m.blocks, { type: "text", content: chunk, stage }],
|
|
216
|
+
};
|
|
237
217
|
}
|
|
238
|
-
|
|
218
|
+
return {
|
|
219
|
+
...m,
|
|
220
|
+
blocks: m.blocks.map((b, idx) =>
|
|
221
|
+
idx === textBlockIndex && b.type === "text"
|
|
222
|
+
? {
|
|
223
|
+
...b,
|
|
224
|
+
content: (b.content || "") + chunk,
|
|
225
|
+
stage,
|
|
226
|
+
}
|
|
227
|
+
: b,
|
|
228
|
+
),
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
};
|
|
239
232
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Pure updater: appends a reasoning delta to the target message's reasoning
|
|
235
|
+
* block (creating it on first delta), applying the stage signal.
|
|
236
|
+
*/
|
|
237
|
+
const applyReasoningDelta = (
|
|
238
|
+
prev: Message[],
|
|
239
|
+
params: StreamingUpdateParams,
|
|
240
|
+
): Message[] => {
|
|
241
|
+
const { messageId, chunk, stage } = params;
|
|
242
|
+
return prev.map((m) => {
|
|
243
|
+
if (m.id !== messageId) return m;
|
|
244
|
+
const reasoningBlockIndex = m.blocks.findIndex(
|
|
245
|
+
(b) => b.type === "reasoning",
|
|
246
|
+
);
|
|
247
|
+
if (reasoningBlockIndex === -1) {
|
|
248
|
+
return {
|
|
249
|
+
...m,
|
|
250
|
+
blocks: [...m.blocks, { type: "reasoning", content: chunk, stage }],
|
|
251
|
+
};
|
|
244
252
|
}
|
|
245
|
-
|
|
246
|
-
|
|
253
|
+
return {
|
|
254
|
+
...m,
|
|
255
|
+
blocks: m.blocks.map((b, idx) =>
|
|
256
|
+
idx === reasoningBlockIndex && b.type === "reasoning"
|
|
257
|
+
? {
|
|
258
|
+
...b,
|
|
259
|
+
content: (b.content || "") + chunk,
|
|
260
|
+
stage,
|
|
261
|
+
}
|
|
262
|
+
: b,
|
|
263
|
+
),
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
};
|
|
247
267
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Pure updater: applies a tool block update. Streaming carries only the
|
|
270
|
+
* `parametersChunk` delta, appended to the accumulated parameters;
|
|
271
|
+
* start/running/end carry the authoritative value and replace wholesale.
|
|
272
|
+
*/
|
|
273
|
+
const applyToolBlockUpdate = (
|
|
274
|
+
prev: Message[],
|
|
275
|
+
params: ToolBlockUpdateCallbackParams,
|
|
276
|
+
): Message[] => {
|
|
277
|
+
const { messageId, id: toolBlockId, parametersChunk, ...updates } = params;
|
|
278
|
+
return prev.map((m) => {
|
|
279
|
+
if (m.id !== messageId) return m;
|
|
280
|
+
const toolBlockIndex = m.blocks.findIndex(
|
|
281
|
+
(b) => b.type === "tool" && b.id === toolBlockId,
|
|
282
|
+
);
|
|
283
|
+
if (toolBlockIndex === -1) {
|
|
284
|
+
return {
|
|
285
|
+
...m,
|
|
286
|
+
blocks: [
|
|
287
|
+
...m.blocks,
|
|
288
|
+
{
|
|
289
|
+
type: "tool",
|
|
290
|
+
id: toolBlockId,
|
|
291
|
+
name: updates.name || "",
|
|
292
|
+
stage: updates.stage || "start",
|
|
293
|
+
parameters: (updates.parameters || "") + (parametersChunk || ""),
|
|
294
|
+
result: updates.result || "",
|
|
295
|
+
...updates,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
};
|
|
252
299
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
300
|
+
return {
|
|
301
|
+
...m,
|
|
302
|
+
blocks: m.blocks.map((b, idx) =>
|
|
303
|
+
idx === toolBlockIndex && b.type === "tool"
|
|
304
|
+
? {
|
|
305
|
+
...b,
|
|
306
|
+
...updates,
|
|
307
|
+
parameters: parametersChunk
|
|
308
|
+
? (b.parameters || "") + parametersChunk
|
|
309
|
+
: updates.parameters !== undefined
|
|
310
|
+
? updates.parameters
|
|
311
|
+
: b.parameters,
|
|
312
|
+
}
|
|
313
|
+
: b,
|
|
314
|
+
),
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
};
|
|
258
318
|
|
|
259
319
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
320
|
+
* Single throttled updater entry for ALL message-state updates. The one
|
|
321
|
+
* 500ms window-concat window replaces the previous three-channel throttles
|
|
322
|
+
* (content/reasoning/tool), so every message-state update — including the
|
|
323
|
+
* tool `running` stage's high-frequency `shortResult`/`result` updates from
|
|
324
|
+
* bash — coalesces into the same window.
|
|
325
|
+
*
|
|
326
|
+
* Semantics: leading edge applies immediately and opens a window; updates
|
|
327
|
+
* arriving within the window are queued in arrival order (FIFO) and applied
|
|
328
|
+
* at the trailing edge as ONE composed updater, so no update is lost and no
|
|
329
|
+
* update is reordered. `flush` applies queued updates immediately (used by
|
|
330
|
+
* end signals and one-shot structural updates); `cancel` drops queued
|
|
331
|
+
* updates (used by refreshMessages after pulling the authoritative snapshot —
|
|
332
|
+
* the queued updates are already contained in it).
|
|
333
|
+
*
|
|
334
|
+
* The FIFO queue structurally replaces the old tool throttle's "drop a
|
|
335
|
+
* tool's buffered streaming deltas when running arrives" logic: a running
|
|
336
|
+
* updater is queued BEHIND the streaming chunks that preceded it and applies
|
|
337
|
+
* after them, so a stale streaming event can never flush after running and
|
|
338
|
+
* regress the stage (yellow dot -> gray). See
|
|
339
|
+
* docs/specs/core/stream-content-updates.md.
|
|
267
340
|
*/
|
|
268
|
-
export function
|
|
269
|
-
|
|
341
|
+
export function createThrottledUpdater<T>(
|
|
342
|
+
apply: (updater: (prev: T) => T) => void,
|
|
270
343
|
wait: number,
|
|
271
344
|
): {
|
|
272
|
-
(
|
|
345
|
+
(updater: (prev: T) => T): void;
|
|
273
346
|
cancel: () => void;
|
|
274
347
|
flush: () => void;
|
|
275
348
|
} {
|
|
276
349
|
let timer: NodeJS.Timeout | null = null;
|
|
277
|
-
let
|
|
350
|
+
let queued: Array<(prev: T) => T> = [];
|
|
278
351
|
|
|
279
352
|
const fire = () => {
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
353
|
+
if (queued.length === 0) return;
|
|
354
|
+
const batch = queued;
|
|
355
|
+
queued = [];
|
|
356
|
+
// Compose the whole batch into a single updater applied in FIFO order —
|
|
357
|
+
// one state commit per trailing edge, no update dropped, no reordering.
|
|
358
|
+
apply((prev) => batch.reduce((acc, upd) => upd(acc), prev));
|
|
287
359
|
};
|
|
288
360
|
|
|
289
|
-
const throttled = (
|
|
290
|
-
if (
|
|
291
|
-
|
|
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
|
-
}
|
|
361
|
+
const throttled = (updater: (prev: T) => T) => {
|
|
362
|
+
if (timer) {
|
|
363
|
+
queued.push(updater);
|
|
312
364
|
return;
|
|
313
365
|
}
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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);
|
|
366
|
+
// Leading edge: apply immediately, then open a window whose trailing
|
|
367
|
+
// edge only carries updates arriving within the window.
|
|
368
|
+
apply(updater);
|
|
369
|
+
timer = setTimeout(() => {
|
|
370
|
+
timer = null;
|
|
371
|
+
fire();
|
|
372
|
+
}, wait);
|
|
331
373
|
};
|
|
332
374
|
|
|
333
375
|
throttled.cancel = () => {
|
|
@@ -335,7 +377,7 @@ export function createToolStreamingThrottle(
|
|
|
335
377
|
clearTimeout(timer);
|
|
336
378
|
timer = null;
|
|
337
379
|
}
|
|
338
|
-
|
|
380
|
+
queued = [];
|
|
339
381
|
};
|
|
340
382
|
|
|
341
383
|
throttled.flush = () => {
|
|
@@ -379,135 +421,16 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
379
421
|
const [latestTotalTokens, setLatestTotalTokens] = useState(0);
|
|
380
422
|
const [maxInputTokens, setMaxInputTokens] = useState(200000);
|
|
381
423
|
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
|
|
424
|
+
// Single throttled entry for ALL message-state updates — one 500ms
|
|
425
|
+
// window-concat window shared by every callback (content/reasoning deltas,
|
|
426
|
+
// tool parametersChunk, tool `running` stage shortResult/result, one-shot
|
|
427
|
+
// structural updates). The tool `running` stage used to bypass throttling
|
|
428
|
+
// (bash shortResult height changes per chunk → layout flicker); routing it
|
|
429
|
+
// through the same window as streaming deltas coalesces it to ≤1 render per
|
|
430
|
+
// window. See createThrottledUpdater + docs/specs/core/stream-content-updates.md.
|
|
431
|
+
const updateMessages = useMemo(
|
|
388
432
|
() =>
|
|
389
|
-
|
|
390
|
-
const { messageId, chunk, stage } = params;
|
|
391
|
-
setMessages((prev) =>
|
|
392
|
-
prev.map((m) => {
|
|
393
|
-
if (m.id !== messageId) return m;
|
|
394
|
-
const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
|
|
395
|
-
if (textBlockIndex === -1) {
|
|
396
|
-
return {
|
|
397
|
-
...m,
|
|
398
|
-
blocks: [...m.blocks, { type: "text", content: chunk, stage }],
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
return {
|
|
402
|
-
...m,
|
|
403
|
-
blocks: m.blocks.map((b, idx) =>
|
|
404
|
-
idx === textBlockIndex && b.type === "text"
|
|
405
|
-
? {
|
|
406
|
-
...b,
|
|
407
|
-
content: (b.content || "") + chunk,
|
|
408
|
-
stage,
|
|
409
|
-
}
|
|
410
|
-
: b,
|
|
411
|
-
),
|
|
412
|
-
};
|
|
413
|
-
}),
|
|
414
|
-
);
|
|
415
|
-
}, 500),
|
|
416
|
-
[],
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
const throttledReasoningUpdate = useMemo(
|
|
420
|
-
() =>
|
|
421
|
-
createStreamingWindowThrottle((params) => {
|
|
422
|
-
const { messageId, chunk, stage } = params;
|
|
423
|
-
setMessages((prev) =>
|
|
424
|
-
prev.map((m) => {
|
|
425
|
-
if (m.id !== messageId) return m;
|
|
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
|
-
}),
|
|
451
|
-
);
|
|
452
|
-
}, 500),
|
|
453
|
-
[],
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
const throttledToolBlockUpdate = useMemo(
|
|
457
|
-
() =>
|
|
458
|
-
createToolStreamingThrottle((params) => {
|
|
459
|
-
const {
|
|
460
|
-
messageId,
|
|
461
|
-
id: toolBlockId,
|
|
462
|
-
parametersChunk,
|
|
463
|
-
...updates
|
|
464
|
-
} = params;
|
|
465
|
-
setMessages((prev) =>
|
|
466
|
-
prev.map((m) => {
|
|
467
|
-
if (m.id !== messageId) return m;
|
|
468
|
-
const toolBlockIndex = m.blocks.findIndex(
|
|
469
|
-
(b) => b.type === "tool" && b.id === toolBlockId,
|
|
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
|
-
}
|
|
489
|
-
return {
|
|
490
|
-
...m,
|
|
491
|
-
blocks: m.blocks.map((b, idx) =>
|
|
492
|
-
idx === toolBlockIndex && b.type === "tool"
|
|
493
|
-
? {
|
|
494
|
-
...b,
|
|
495
|
-
...updates,
|
|
496
|
-
// Streaming carries only the delta; append it to the
|
|
497
|
-
// accumulated parameters. start/running/end carry the
|
|
498
|
-
// authoritative value and replace wholesale.
|
|
499
|
-
parameters: parametersChunk
|
|
500
|
-
? (b.parameters || "") + parametersChunk
|
|
501
|
-
: updates.parameters !== undefined
|
|
502
|
-
? updates.parameters
|
|
503
|
-
: b.parameters,
|
|
504
|
-
}
|
|
505
|
-
: b,
|
|
506
|
-
),
|
|
507
|
-
};
|
|
508
|
-
}),
|
|
509
|
-
);
|
|
510
|
-
}, 500),
|
|
433
|
+
createThrottledUpdater<Message[]>((updater) => setMessages(updater), 500),
|
|
511
434
|
[],
|
|
512
435
|
);
|
|
513
436
|
|
|
@@ -515,21 +438,15 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
515
438
|
isExpandedRef.current = isExpanded;
|
|
516
439
|
if (isExpanded) {
|
|
517
440
|
// Cancel pending throttled updates so the frozen expanded view isn't overwritten
|
|
518
|
-
|
|
519
|
-
throttledReasoningUpdate.cancel();
|
|
520
|
-
throttledToolBlockUpdate.cancel();
|
|
441
|
+
updateMessages.cancel();
|
|
521
442
|
}
|
|
522
|
-
}, [
|
|
523
|
-
isExpanded,
|
|
524
|
-
throttledContentUpdate,
|
|
525
|
-
throttledReasoningUpdate,
|
|
526
|
-
throttledToolBlockUpdate,
|
|
527
|
-
]);
|
|
443
|
+
}, [isExpanded, updateMessages]);
|
|
528
444
|
|
|
529
445
|
const [isLoading, setIsLoading] = useState(false);
|
|
530
446
|
const [sessionId, setSessionId] = useState("");
|
|
531
447
|
const [isCommandRunning, setIsCommandRunning] = useState(false);
|
|
532
448
|
const [isCompacting, setIsCompacting] = useState(false);
|
|
449
|
+
const [compactionStream, setCompactionStream] = useState("");
|
|
533
450
|
const [currentModel, setCurrentModelState] = useState("");
|
|
534
451
|
const [configuredModels, setConfiguredModels] = useState<string[]>([]);
|
|
535
452
|
const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]);
|
|
@@ -553,6 +470,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
553
470
|
const [agentDefinitions, setAgentDefinitions] = useState<
|
|
554
471
|
SubagentConfiguration[]
|
|
555
472
|
>([]);
|
|
473
|
+
// Skill metadata (for /skills overlay)
|
|
474
|
+
const [skills, setSkills] = useState<SkillMetadata[]>([]);
|
|
556
475
|
|
|
557
476
|
// Permission state
|
|
558
477
|
const [permissionMode, setPermissionModeState] = useState<PermissionMode>(
|
|
@@ -603,8 +522,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
603
522
|
const [remountKey, setRemountKey] = useState(0);
|
|
604
523
|
|
|
605
524
|
// Full terminal clear + remount so Ink's append-only <Static> re-renders.
|
|
606
|
-
// Used on structural actions (/clear, /compact, rewind, ctrl-o
|
|
607
|
-
//
|
|
525
|
+
// Used on structural actions (/clear, /compact, rewind, ctrl-o) where stale
|
|
526
|
+
// Static output must not linger on screen.
|
|
608
527
|
const forceRemount = useCallback(() => {
|
|
609
528
|
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", () => {
|
|
610
529
|
setRemountKey((prev) => prev + 1);
|
|
@@ -622,10 +541,19 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
622
541
|
const refreshMessages = useCallback(() => {
|
|
623
542
|
if (!isExpandedRef.current && agentRef.current) {
|
|
624
543
|
const msgs = agentRef.current.messages.map(snapshotMessage);
|
|
544
|
+
// Snapshot-safe: the full-list replacement makes the SDK state
|
|
545
|
+
// authoritative. Any update still queued inside the 500ms throttle
|
|
546
|
+
// window was applied to the SDK before this pull, so it is already
|
|
547
|
+
// contained in `msgs` — dropping it prevents the trailing-edge flush
|
|
548
|
+
// from re-appending the pre-refresh chunk on top of the snapshot
|
|
549
|
+
// (first-word duplication). Updates arriving after the pull start fresh
|
|
550
|
+
// windows and append on top of the snapshot. See
|
|
551
|
+
// docs/specs/core/stream-content-updates.md.
|
|
552
|
+
updateMessages.cancel();
|
|
625
553
|
setMessages(msgs);
|
|
626
554
|
setLatestTotalTokens(extractLatestTotalTokens(msgs));
|
|
627
555
|
}
|
|
628
|
-
}, []);
|
|
556
|
+
}, [updateMessages]);
|
|
629
557
|
|
|
630
558
|
// Permission confirmation methods with queue support
|
|
631
559
|
const showConfirmation = useCallback(
|
|
@@ -666,43 +594,55 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
666
594
|
|
|
667
595
|
const callbacks: AgentCallbacks = {
|
|
668
596
|
// ── Incremental message updates (no full-list pushes) ──────
|
|
597
|
+
// All of these funnel through the single throttled `updateMessages`
|
|
598
|
+
// entry, so every message-state update shares one 500ms window.
|
|
669
599
|
onUserMessageAdded: () => {
|
|
670
600
|
if (isExpandedRef.current || !agentRef.current) return;
|
|
671
601
|
const msgs = agentRef.current.messages;
|
|
672
602
|
const last = msgs[msgs.length - 1];
|
|
673
603
|
if (!last || last.role !== "user") return;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
604
|
+
// Eager snapshot at callback time — React defers updater execution to
|
|
605
|
+
// the batch flush, so evaluating snapshotMessage inside the updater
|
|
606
|
+
// would read the SDK message after its in-place mutations.
|
|
607
|
+
const snapshot = snapshotMessage(last);
|
|
608
|
+
updateMessages((prev) =>
|
|
609
|
+
prev.some((m) => m.id === last.id) ? prev : [...prev, snapshot],
|
|
678
610
|
);
|
|
611
|
+
// One-shot structural update — flush immediately so the message
|
|
612
|
+
// card appears at once (queuing it gains no coalescing).
|
|
613
|
+
updateMessages.flush();
|
|
679
614
|
},
|
|
680
615
|
onAssistantMessageAdded: (messageId: string) => {
|
|
681
616
|
if (isExpandedRef.current || !agentRef.current) return;
|
|
682
617
|
const msg = agentRef.current.messages.find((m) => m.id === messageId);
|
|
683
618
|
if (!msg) return;
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
619
|
+
// Eager snapshot (see onUserMessageAdded): `addAssistantMessage()`
|
|
620
|
+
// fires this callback BEFORE the first delta writes into the shared
|
|
621
|
+
// message, so capturing here copies the pre-mutation (empty) blocks.
|
|
622
|
+
const snapshot = snapshotMessage(msg);
|
|
623
|
+
updateMessages((prev) =>
|
|
624
|
+
prev.some((m) => m.id === messageId) ? prev : [...prev, snapshot],
|
|
688
625
|
);
|
|
626
|
+
updateMessages.flush();
|
|
689
627
|
},
|
|
690
628
|
onAssistantContentUpdated: (params) => {
|
|
691
629
|
if (isExpandedRef.current) return;
|
|
692
|
-
|
|
630
|
+
updateMessages((prev) => applyContentDelta(prev, params));
|
|
631
|
+
if (params.stage === "end") updateMessages.flush();
|
|
693
632
|
},
|
|
694
633
|
onAssistantReasoningUpdated: (params) => {
|
|
695
634
|
if (isExpandedRef.current) return;
|
|
696
|
-
|
|
635
|
+
updateMessages((prev) => applyReasoningDelta(prev, params));
|
|
636
|
+
if (params.stage === "end") updateMessages.flush();
|
|
697
637
|
},
|
|
698
638
|
onToolBlockUpdated: (params) => {
|
|
699
639
|
if (isExpandedRef.current) return;
|
|
700
|
-
|
|
701
|
-
if (params.stage === "end")
|
|
640
|
+
updateMessages((prev) => applyToolBlockUpdate(prev, params));
|
|
641
|
+
if (params.stage === "end") updateMessages.flush();
|
|
702
642
|
},
|
|
703
643
|
onErrorBlockAdded: (error: string) => {
|
|
704
644
|
if (isExpandedRef.current) return;
|
|
705
|
-
|
|
645
|
+
updateMessages((prev) => {
|
|
706
646
|
// Append to the LAST message only if it is an assistant message
|
|
707
647
|
// (the current turn's in-flight reply). If the last message is a
|
|
708
648
|
// user message, the error belongs BELOW it — create a new
|
|
@@ -730,10 +670,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
730
670
|
},
|
|
731
671
|
];
|
|
732
672
|
});
|
|
673
|
+
// One-shot structural update — flush immediately (errors must not be
|
|
674
|
+
// delayed by a streaming window).
|
|
675
|
+
updateMessages.flush();
|
|
733
676
|
},
|
|
734
677
|
onAddBangMessage: (command, messageId) => {
|
|
735
678
|
if (isExpandedRef.current) return;
|
|
736
|
-
|
|
679
|
+
updateMessages((prev) =>
|
|
737
680
|
prev.some((m) => m.id === messageId)
|
|
738
681
|
? prev
|
|
739
682
|
: [
|
|
@@ -754,10 +697,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
754
697
|
},
|
|
755
698
|
],
|
|
756
699
|
);
|
|
700
|
+
updateMessages.flush();
|
|
757
701
|
},
|
|
758
702
|
onUpdateBangMessage: (command, output, messageId) => {
|
|
759
703
|
if (isExpandedRef.current) return;
|
|
760
|
-
|
|
704
|
+
updateMessages((prev) =>
|
|
761
705
|
prev.map((m) =>
|
|
762
706
|
m.id === messageId
|
|
763
707
|
? {
|
|
@@ -774,7 +718,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
774
718
|
},
|
|
775
719
|
onCompleteBangMessage: (command, exitCode, messageId, output) => {
|
|
776
720
|
if (isExpandedRef.current) return;
|
|
777
|
-
|
|
721
|
+
updateMessages((prev) =>
|
|
778
722
|
prev.map((m) =>
|
|
779
723
|
m.id === messageId
|
|
780
724
|
? {
|
|
@@ -794,6 +738,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
794
738
|
: m,
|
|
795
739
|
),
|
|
796
740
|
);
|
|
741
|
+
// Completion signal — flush so the final state applies immediately.
|
|
742
|
+
updateMessages.flush();
|
|
797
743
|
},
|
|
798
744
|
onLatestTotalTokensChange: (tokens) => {
|
|
799
745
|
setLatestTotalTokens(tokens);
|
|
@@ -806,6 +752,14 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
806
752
|
},
|
|
807
753
|
onCompactionStateChange: (isCompactingState) => {
|
|
808
754
|
setIsCompacting(isCompactingState);
|
|
755
|
+
if (!isCompactingState) {
|
|
756
|
+
setCompactionStream("");
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
onCompactionContentUpdate: (content) => {
|
|
760
|
+
// The SDK delivers the accumulated compaction text; the loading
|
|
761
|
+
// indicator renders only its last 30 characters (streaming tail).
|
|
762
|
+
setCompactionStream(content);
|
|
809
763
|
},
|
|
810
764
|
onBackgroundTasksChange: async (tasks) => {
|
|
811
765
|
setBackgroundTasks([...tasks]);
|
|
@@ -936,6 +890,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
936
890
|
const initialAgentDefinitions =
|
|
937
891
|
agent.getSubagentConfigurations?.() || [];
|
|
938
892
|
setAgentDefinitions(initialAgentDefinitions);
|
|
893
|
+
|
|
894
|
+
// Get initial skill metadata
|
|
895
|
+
const initialSkills = agent.getSkillMetadata?.() || [];
|
|
896
|
+
setSkills(initialSkills);
|
|
939
897
|
} catch (error) {
|
|
940
898
|
console.error("Failed to initialize AI manager:", error);
|
|
941
899
|
}
|
|
@@ -955,9 +913,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
955
913
|
originalCwd,
|
|
956
914
|
model,
|
|
957
915
|
initialPermissionMode,
|
|
958
|
-
|
|
959
|
-
throttledReasoningUpdate,
|
|
960
|
-
throttledToolBlockUpdate,
|
|
916
|
+
updateMessages,
|
|
961
917
|
mcpServers,
|
|
962
918
|
],
|
|
963
919
|
);
|
|
@@ -977,6 +933,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
977
933
|
setMcpServerStatuses([]);
|
|
978
934
|
setSlashCommands([]);
|
|
979
935
|
setAgentDefinitions([]);
|
|
936
|
+
setSkills([]);
|
|
980
937
|
setSessionId("");
|
|
981
938
|
setIsLoading(false);
|
|
982
939
|
setLatestTotalTokens(0);
|
|
@@ -996,9 +953,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
996
953
|
// Cleanup on unmount
|
|
997
954
|
useEffect(() => {
|
|
998
955
|
return () => {
|
|
999
|
-
|
|
1000
|
-
throttledReasoningUpdate.cancel();
|
|
1001
|
-
throttledToolBlockUpdate.cancel();
|
|
956
|
+
updateMessages.cancel();
|
|
1002
957
|
if (agentRef.current) {
|
|
1003
958
|
try {
|
|
1004
959
|
// Display usage summary before cleanup
|
|
@@ -1012,19 +967,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1012
967
|
agentRef.current.destroy();
|
|
1013
968
|
}
|
|
1014
969
|
};
|
|
1015
|
-
}, [
|
|
1016
|
-
throttledContentUpdate,
|
|
1017
|
-
throttledReasoningUpdate,
|
|
1018
|
-
throttledToolBlockUpdate,
|
|
1019
|
-
]);
|
|
1020
|
-
|
|
1021
|
-
// Trigger WorktreeRemove hook BEFORE agent destruction
|
|
1022
|
-
const triggerWorktreeRemoveHook = useCallback(
|
|
1023
|
-
async (worktreePath: string) => {
|
|
1024
|
-
await agentRef.current?.triggerWorktreeRemoveHook(worktreePath);
|
|
1025
|
-
},
|
|
1026
|
-
[],
|
|
1027
|
-
);
|
|
970
|
+
}, [updateMessages]);
|
|
1028
971
|
|
|
1029
972
|
// Send message function (including judgment logic)
|
|
1030
973
|
const sendMessage = useCallback(
|
|
@@ -1354,6 +1297,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1354
1297
|
getConfiguredModels,
|
|
1355
1298
|
setModel,
|
|
1356
1299
|
isCompacting,
|
|
1300
|
+
compactionStream,
|
|
1357
1301
|
mcpServers: mcpServerStatuses,
|
|
1358
1302
|
connectMcpServer,
|
|
1359
1303
|
disconnectMcpServer,
|
|
@@ -1366,6 +1310,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1366
1310
|
slashCommands,
|
|
1367
1311
|
hasSlashCommand,
|
|
1368
1312
|
agentDefinitions,
|
|
1313
|
+
skills,
|
|
1369
1314
|
permissionMode,
|
|
1370
1315
|
setPermissionMode,
|
|
1371
1316
|
isConfirmationVisible,
|
|
@@ -1387,7 +1332,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
1387
1332
|
version,
|
|
1388
1333
|
workdir,
|
|
1389
1334
|
recreateAgent,
|
|
1390
|
-
triggerWorktreeRemoveHook,
|
|
1391
1335
|
};
|
|
1392
1336
|
|
|
1393
1337
|
return (
|