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.
Files changed (244) hide show
  1. package/bin/wave-code.js +8 -2
  2. package/dist/bundle/wave.mjs +3207 -0
  3. package/package.json +24 -25
  4. package/src/commands/update.ts +2 -7
  5. package/src/components/App.tsx +3 -7
  6. package/src/components/BtwDisplay.tsx +10 -1
  7. package/src/components/ChatInterface.tsx +21 -52
  8. package/src/components/ConfirmationDetails.tsx +144 -37
  9. package/src/components/DiffDisplay.tsx +335 -301
  10. package/src/components/InputBox.tsx +3 -1
  11. package/src/components/LoadingIndicator.tsx +16 -2
  12. package/src/components/MessageBlockItem.tsx +2 -4
  13. package/src/components/MessageList.tsx +75 -14
  14. package/src/components/ReasoningDisplay.tsx +2 -4
  15. package/src/components/SessionSelector.tsx +82 -11
  16. package/src/components/StatusCommand.tsx +2 -9
  17. package/src/components/ToolDisplay.tsx +2 -4
  18. package/src/contexts/useChat.tsx +172 -350
  19. package/src/daemon/commands.ts +57 -27
  20. package/src/daemon/jsonRpcClient.ts +1 -1
  21. package/src/daemon/socketClient.ts +2 -2
  22. package/src/hooks/useInputManager.ts +1 -1
  23. package/src/index.ts +57 -10
  24. package/src/managers/inputReducer.ts +186 -104
  25. package/src/print-cli.ts +21 -17
  26. package/src/reducers/selectorReducer.ts +10 -0
  27. package/src/session-selector-cli.tsx +269 -28
  28. package/src/stdio/agentBridge.ts +22 -21
  29. package/src/stdio/daemonServer.ts +3 -1
  30. package/src/stdio/jsonRpcConnection.ts +19 -1
  31. package/src/stdio/stdioServer.ts +17 -0
  32. package/src/utils/clipboard.ts +21 -0
  33. package/src/utils/highlightUtils.ts +46 -1
  34. package/src/utils/readPackageJson.ts +29 -0
  35. package/src/utils/streamingText.ts +11 -0
  36. package/src/utils/worktree.ts +191 -43
  37. package/dist/cli.d.ts +0 -6
  38. package/dist/cli.js +0 -53
  39. package/dist/commands/plugin/disable.d.ts +0 -5
  40. package/dist/commands/plugin/disable.js +0 -15
  41. package/dist/commands/plugin/enable.d.ts +0 -5
  42. package/dist/commands/plugin/enable.js +0 -15
  43. package/dist/commands/plugin/install.d.ts +0 -5
  44. package/dist/commands/plugin/install.js +0 -21
  45. package/dist/commands/plugin/list.d.ts +0 -1
  46. package/dist/commands/plugin/list.js +0 -31
  47. package/dist/commands/plugin/marketplace.d.ts +0 -10
  48. package/dist/commands/plugin/marketplace.js +0 -90
  49. package/dist/commands/plugin/uninstall.d.ts +0 -3
  50. package/dist/commands/plugin/uninstall.js +0 -16
  51. package/dist/commands/plugin/update.d.ts +0 -3
  52. package/dist/commands/plugin/update.js +0 -15
  53. package/dist/commands/update.d.ts +0 -1
  54. package/dist/commands/update.js +0 -97
  55. package/dist/components/AgentsManager.d.ts +0 -7
  56. package/dist/components/AgentsManager.js +0 -109
  57. package/dist/components/App.d.ts +0 -9
  58. package/dist/components/App.js +0 -76
  59. package/dist/components/BackgroundTaskManager.d.ts +0 -5
  60. package/dist/components/BackgroundTaskManager.js +0 -99
  61. package/dist/components/BangDisplay.d.ts +0 -8
  62. package/dist/components/BangDisplay.js +0 -17
  63. package/dist/components/BtwDisplay.d.ts +0 -7
  64. package/dist/components/BtwDisplay.js +0 -19
  65. package/dist/components/ChatInterface.d.ts +0 -2
  66. package/dist/components/ChatInterface.js +0 -61
  67. package/dist/components/CommandSelector.d.ts +0 -10
  68. package/dist/components/CommandSelector.js +0 -66
  69. package/dist/components/CompactDisplay.d.ts +0 -8
  70. package/dist/components/CompactDisplay.js +0 -13
  71. package/dist/components/ConfirmationDetails.d.ts +0 -9
  72. package/dist/components/ConfirmationDetails.js +0 -45
  73. package/dist/components/ConfirmationSelector.d.ts +0 -13
  74. package/dist/components/ConfirmationSelector.js +0 -127
  75. package/dist/components/DiffDisplay.d.ts +0 -8
  76. package/dist/components/DiffDisplay.js +0 -201
  77. package/dist/components/DiscoverView.d.ts +0 -2
  78. package/dist/components/DiscoverView.js +0 -37
  79. package/dist/components/FileSelector.d.ts +0 -11
  80. package/dist/components/FileSelector.js +0 -50
  81. package/dist/components/HelpView.d.ts +0 -7
  82. package/dist/components/HelpView.js +0 -74
  83. package/dist/components/HistorySearch.d.ts +0 -8
  84. package/dist/components/HistorySearch.js +0 -66
  85. package/dist/components/InputBox.d.ts +0 -24
  86. package/dist/components/InputBox.js +0 -149
  87. package/dist/components/InstalledView.d.ts +0 -2
  88. package/dist/components/InstalledView.js +0 -45
  89. package/dist/components/LoadingIndicator.d.ts +0 -10
  90. package/dist/components/LoadingIndicator.js +0 -6
  91. package/dist/components/LoginCommand.d.ts +0 -5
  92. package/dist/components/LoginCommand.js +0 -153
  93. package/dist/components/Markdown.d.ts +0 -5
  94. package/dist/components/Markdown.js +0 -127
  95. package/dist/components/MarketplaceAddForm.d.ts +0 -2
  96. package/dist/components/MarketplaceAddForm.js +0 -55
  97. package/dist/components/MarketplaceDetail.d.ts +0 -2
  98. package/dist/components/MarketplaceDetail.js +0 -74
  99. package/dist/components/MarketplaceList.d.ts +0 -8
  100. package/dist/components/MarketplaceList.js +0 -17
  101. package/dist/components/MarketplaceView.d.ts +0 -2
  102. package/dist/components/MarketplaceView.js +0 -41
  103. package/dist/components/McpManager.d.ts +0 -9
  104. package/dist/components/McpManager.js +0 -88
  105. package/dist/components/MessageBlockItem.d.ts +0 -8
  106. package/dist/components/MessageBlockItem.js +0 -15
  107. package/dist/components/MessageList.d.ts +0 -10
  108. package/dist/components/MessageList.js +0 -80
  109. package/dist/components/ModelSelector.d.ts +0 -8
  110. package/dist/components/ModelSelector.js +0 -45
  111. package/dist/components/Notifications.d.ts +0 -7
  112. package/dist/components/Notifications.js +0 -9
  113. package/dist/components/PlanDisplay.d.ts +0 -7
  114. package/dist/components/PlanDisplay.js +0 -6
  115. package/dist/components/PluginDetail.d.ts +0 -2
  116. package/dist/components/PluginDetail.js +0 -87
  117. package/dist/components/PluginList.d.ts +0 -13
  118. package/dist/components/PluginList.js +0 -15
  119. package/dist/components/PluginManagerShell.d.ts +0 -6
  120. package/dist/components/PluginManagerShell.js +0 -92
  121. package/dist/components/PluginManagerTypes.d.ts +0 -35
  122. package/dist/components/PluginManagerTypes.js +0 -1
  123. package/dist/components/QueuedMessageList.d.ts +0 -2
  124. package/dist/components/QueuedMessageList.js +0 -18
  125. package/dist/components/ReasoningDisplay.d.ts +0 -8
  126. package/dist/components/ReasoningDisplay.js +0 -13
  127. package/dist/components/RewindCommand.d.ts +0 -12
  128. package/dist/components/RewindCommand.js +0 -78
  129. package/dist/components/SessionSelector.d.ts +0 -12
  130. package/dist/components/SessionSelector.js +0 -47
  131. package/dist/components/StatusCommand.d.ts +0 -5
  132. package/dist/components/StatusCommand.js +0 -28
  133. package/dist/components/StatusLine.d.ts +0 -6
  134. package/dist/components/StatusLine.js +0 -9
  135. package/dist/components/TaskList.d.ts +0 -5
  136. package/dist/components/TaskList.js +0 -197
  137. package/dist/components/TaskNotificationMessage.d.ts +0 -5
  138. package/dist/components/TaskNotificationMessage.js +0 -12
  139. package/dist/components/ToolDisplay.d.ts +0 -9
  140. package/dist/components/ToolDisplay.js +0 -55
  141. package/dist/components/WorkflowManager.d.ts +0 -5
  142. package/dist/components/WorkflowManager.js +0 -102
  143. package/dist/components/WorktreeExitPrompt.d.ts +0 -12
  144. package/dist/components/WorktreeExitPrompt.js +0 -35
  145. package/dist/constants/commands.d.ts +0 -2
  146. package/dist/constants/commands.js +0 -92
  147. package/dist/contexts/PluginManagerContext.d.ts +0 -3
  148. package/dist/contexts/PluginManagerContext.js +0 -9
  149. package/dist/contexts/useAppConfig.d.ts +0 -10
  150. package/dist/contexts/useAppConfig.js +0 -13
  151. package/dist/contexts/useChat.d.ts +0 -101
  152. package/dist/contexts/useChat.js +0 -992
  153. package/dist/contracts/status.d.ts +0 -7
  154. package/dist/contracts/status.js +0 -1
  155. package/dist/daemon/commands.d.ts +0 -49
  156. package/dist/daemon/commands.js +0 -341
  157. package/dist/daemon/jsonRpcClient.d.ts +0 -38
  158. package/dist/daemon/jsonRpcClient.js +0 -129
  159. package/dist/daemon/socketClient.d.ts +0 -13
  160. package/dist/daemon/socketClient.js +0 -26
  161. package/dist/daemon-cli.d.ts +0 -10
  162. package/dist/daemon-cli.js +0 -15
  163. package/dist/hooks/useInputManager.d.ts +0 -75
  164. package/dist/hooks/useInputManager.js +0 -583
  165. package/dist/hooks/usePluginManager.d.ts +0 -4
  166. package/dist/hooks/usePluginManager.js +0 -265
  167. package/dist/hooks/useTasks.d.ts +0 -1
  168. package/dist/hooks/useTasks.js +0 -5
  169. package/dist/index.d.ts +0 -4
  170. package/dist/index.js +0 -447
  171. package/dist/managers/inputHandlers.d.ts +0 -30
  172. package/dist/managers/inputHandlers.js +0 -625
  173. package/dist/managers/inputReducer.d.ts +0 -271
  174. package/dist/managers/inputReducer.js +0 -1121
  175. package/dist/plugin-manager-cli.d.ts +0 -5
  176. package/dist/plugin-manager-cli.js +0 -12
  177. package/dist/print-cli.d.ts +0 -10
  178. package/dist/print-cli.js +0 -209
  179. package/dist/reducers/agentsManagerReducer.d.ts +0 -26
  180. package/dist/reducers/agentsManagerReducer.js +0 -54
  181. package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
  182. package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
  183. package/dist/reducers/confirmationReducer.d.ts +0 -37
  184. package/dist/reducers/confirmationReducer.js +0 -193
  185. package/dist/reducers/helpSelectorReducer.d.ts +0 -25
  186. package/dist/reducers/helpSelectorReducer.js +0 -52
  187. package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
  188. package/dist/reducers/marketplaceAddFormReducer.js +0 -58
  189. package/dist/reducers/mcpManagerReducer.d.ts +0 -37
  190. package/dist/reducers/mcpManagerReducer.js +0 -74
  191. package/dist/reducers/pluginDetailReducer.d.ts +0 -32
  192. package/dist/reducers/pluginDetailReducer.js +0 -70
  193. package/dist/reducers/questionReducer.d.ts +0 -82
  194. package/dist/reducers/questionReducer.js +0 -375
  195. package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
  196. package/dist/reducers/rewindSelectorReducer.js +0 -37
  197. package/dist/reducers/selectorReducer.d.ts +0 -23
  198. package/dist/reducers/selectorReducer.js +0 -42
  199. package/dist/reducers/workflowManagerReducer.d.ts +0 -40
  200. package/dist/reducers/workflowManagerReducer.js +0 -100
  201. package/dist/session-selector-cli.d.ts +0 -3
  202. package/dist/session-selector-cli.js +0 -25
  203. package/dist/stdio/agentBridge.d.ts +0 -139
  204. package/dist/stdio/agentBridge.js +0 -999
  205. package/dist/stdio/daemonServer.d.ts +0 -67
  206. package/dist/stdio/daemonServer.js +0 -191
  207. package/dist/stdio/index.d.ts +0 -5
  208. package/dist/stdio/index.js +0 -5
  209. package/dist/stdio/jsonRpcConnection.d.ts +0 -30
  210. package/dist/stdio/jsonRpcConnection.js +0 -127
  211. package/dist/stdio/protocol.d.ts +0 -41
  212. package/dist/stdio/protocol.js +0 -26
  213. package/dist/stdio/stdioServer.d.ts +0 -28
  214. package/dist/stdio/stdioServer.js +0 -36
  215. package/dist/stdio-cli.d.ts +0 -8
  216. package/dist/stdio-cli.js +0 -15
  217. package/dist/types.d.ts +0 -20
  218. package/dist/types.js +0 -1
  219. package/dist/utils/bracketedPaste.d.ts +0 -39
  220. package/dist/utils/bracketedPaste.js +0 -122
  221. package/dist/utils/clipboard.d.ts +0 -21
  222. package/dist/utils/clipboard.js +0 -347
  223. package/dist/utils/constants.d.ts +0 -16
  224. package/dist/utils/constants.js +0 -18
  225. package/dist/utils/highlightUtils.d.ts +0 -1
  226. package/dist/utils/highlightUtils.js +0 -93
  227. package/dist/utils/inputUtils.d.ts +0 -17
  228. package/dist/utils/inputUtils.js +0 -67
  229. package/dist/utils/logger.d.ts +0 -71
  230. package/dist/utils/logger.js +0 -259
  231. package/dist/utils/markdownTable.d.ts +0 -34
  232. package/dist/utils/markdownTable.js +0 -302
  233. package/dist/utils/rewindCheckpoints.d.ts +0 -8
  234. package/dist/utils/rewindCheckpoints.js +0 -17
  235. package/dist/utils/throttle.d.ts +0 -14
  236. package/dist/utils/throttle.js +0 -55
  237. package/dist/utils/toolParameterTransforms.d.ts +0 -18
  238. package/dist/utils/toolParameterTransforms.js +0 -73
  239. package/dist/utils/usageSummary.d.ts +0 -28
  240. package/dist/utils/usageSummary.js +0 -121
  241. package/dist/utils/version.d.ts +0 -1
  242. package/dist/utils/version.js +0 -4
  243. package/dist/utils/worktree.d.ts +0 -35
  244. package/dist/utils/worktree.js +0 -219
@@ -1,992 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useCallback, useRef, useEffect, useState, useMemo, } from "react";
3
- import { useInput, useStdout } from "ink";
4
- import { useAppConfig } from "./useAppConfig.js";
5
- import { Agent, OPERATION_CANCELLED_BY_USER, extractLatestTotalTokens, } from "wave-agent-sdk";
6
- import { logger } from "../utils/logger.js";
7
- import { displayUsageSummary } from "../utils/usageSummary.js";
8
- import { expandLongTextPlaceholders } from "../managers/inputHandlers.js";
9
- const ChatContext = createContext(null);
10
- export const useChat = () => {
11
- const context = useContext(ChatContext);
12
- if (!context) {
13
- throw new Error("useChat must be used within ChatProvider");
14
- }
15
- return context;
16
- };
17
- /**
18
- * Snapshot a SDK message for consumer state. The SDK mutates its internal
19
- * message blocks in-place BEFORE firing the delta callback (it writes the full
20
- * accumulated value to the shared block, then computes the chunk delta by
21
- * slicing the new value). A consumer that pushed the SDK message object by
22
- * live reference would read the already-updated block and append the delta
23
- * again — the first delta is double-counted ("LetLet me think..."), affecting
24
- * reasoning and text content alike. See docs/specs/core/stream-content-updates.md.
25
- * The clone must be at least one layer deep (message + blocks) so the in-place
26
- * block mutation never leaks into consumer state.
27
- */
28
- const snapshotMessage = (message) => ({
29
- ...message,
30
- blocks: message.blocks.map((block) => ({ ...block })),
31
- });
32
- /**
33
- * Window-concat throttle for pure-delta streaming updates: chunks arriving
34
- * within the cooldown window are merged so no delta is lost (a dropped delta
35
- * would permanently lose content, unlike the accumulated-payload throttle it
36
- * replaces). Leading edge fires immediately; the trailing edge carries only
37
- * chunks that arrived within the window. `end` flushes any pending deltas
38
- * first, then forwards the end signal right away.
39
- */
40
- function createStreamingWindowThrottle(fn, wait) {
41
- let timer = null;
42
- let pending = null;
43
- const fire = (stage) => {
44
- if (pending) {
45
- fn({ ...pending, stage });
46
- pending = null;
47
- }
48
- };
49
- const throttled = (params) => {
50
- if (params.stage === "end") {
51
- // Flush any deltas still pending inside the cooldown window first
52
- if (timer) {
53
- clearTimeout(timer);
54
- timer = null;
55
- }
56
- fire("streaming");
57
- fn(params);
58
- return;
59
- }
60
- if (pending) {
61
- pending.chunk += params.chunk;
62
- }
63
- else {
64
- pending = { messageId: params.messageId, chunk: params.chunk };
65
- }
66
- if (!timer) {
67
- // Leading edge: fire the current delta immediately, then reset pending so
68
- // the trailing edge only carries chunks arriving within this window
69
- fire("streaming");
70
- timer = setTimeout(() => {
71
- timer = null;
72
- fire("streaming");
73
- }, wait);
74
- }
75
- };
76
- throttled.cancel = () => {
77
- if (timer) {
78
- clearTimeout(timer);
79
- timer = null;
80
- }
81
- pending = null;
82
- };
83
- throttled.flush = () => {
84
- if (timer) {
85
- clearTimeout(timer);
86
- timer = null;
87
- }
88
- fire("streaming");
89
- };
90
- return throttled;
91
- }
92
- /**
93
- * Per-tool window-concat throttle for pure-delta tool parameter streaming:
94
- * `parametersChunk` deltas are accumulated independently per tool block id
95
- * within the cooldown window, so interleaved multi-tool streams lose no delta
96
- * (a plain throttle's single last-args slot would drop every earlier tool's
97
- * deltas, leaving the first tool without streaming parameters). `start` /
98
- * `running` apply immediately (one-shot snapshots); `end` flushes pending
99
- * streaming deltas first, then applies the authoritative parameters/result.
100
- */
101
- export function createToolStreamingThrottle(fn, wait) {
102
- let timer = null;
103
- let pending = null;
104
- const fire = () => {
105
- if (pending && pending.chunks.size > 0) {
106
- const { messageId, chunks } = pending;
107
- pending = null;
108
- for (const [id, chunk] of chunks) {
109
- fn({ messageId, id, parametersChunk: chunk, stage: "streaming" });
110
- }
111
- }
112
- };
113
- const throttled = (params) => {
114
- if (params.stage === "end") {
115
- // Flush any deltas still pending inside the cooldown window first
116
- if (timer) {
117
- clearTimeout(timer);
118
- timer = null;
119
- }
120
- fire();
121
- fn(params);
122
- return;
123
- }
124
- if (params.stage === "streaming") {
125
- if (!pending) {
126
- pending = { messageId: params.messageId, chunks: new Map() };
127
- }
128
- const prev = pending.chunks.get(params.id) || "";
129
- pending.chunks.set(params.id, prev + (params.parametersChunk || ""));
130
- if (!timer) {
131
- timer = setTimeout(() => {
132
- timer = null;
133
- fire();
134
- }, wait);
135
- }
136
- return;
137
- }
138
- // start / running — one-shot snapshots applied immediately. Drop this
139
- // tool's buffered streaming deltas first: start/running carry the
140
- // authoritative parameters, and a pending timer would otherwise fire late
141
- // with a stale `streaming` event, regressing this tool block's stage back
142
- // to streaming (yellow dot -> gray) mid-execution. Other tools' in-flight
143
- // chunks are kept so interleaved multi-tool streaming still accumulates.
144
- if (pending) {
145
- pending.chunks.delete(params.id);
146
- if (pending.chunks.size === 0) {
147
- pending = null;
148
- if (timer) {
149
- clearTimeout(timer);
150
- timer = null;
151
- }
152
- }
153
- }
154
- fn(params);
155
- };
156
- throttled.cancel = () => {
157
- if (timer) {
158
- clearTimeout(timer);
159
- timer = null;
160
- }
161
- pending = null;
162
- };
163
- throttled.flush = () => {
164
- if (timer) {
165
- clearTimeout(timer);
166
- timer = null;
167
- }
168
- fire();
169
- };
170
- return throttled;
171
- }
172
- export const ChatProvider = ({ children, bypassPermissions, permissionMode: initialPermissionMode, pluginDirs, additionalDirectories, tools, allowedTools, disallowedTools, workdir, worktreeSession, originalCwd, version, model, mcpServers, }) => {
173
- const { restoreSessionId, continueLastSession } = useAppConfig();
174
- const { stdout } = useStdout();
175
- // Message Display State
176
- const [isExpanded, setIsExpanded] = useState(false);
177
- const isExpandedRef = useRef(isExpanded);
178
- const [isTaskListVisible, setIsTaskListVisible] = useState(true);
179
- const [isBtwActive, setIsBtwActive] = useState(false);
180
- const [messages, setMessages] = useState([]);
181
- const [latestTotalTokens, setLatestTotalTokens] = useState(0);
182
- const [maxInputTokens, setMaxInputTokens] = useState(200000);
183
- // Throttled incremental streaming updaters — 500ms window-concat, the same
184
- // interval as the pre-incremental throttledSetMessages. Chunks are pure
185
- // deltas: within-window chunks are merged so none is dropped, and
186
- // `stage === "end"` flushes pending deltas + applies the end signal
187
- // immediately so completion results are never delayed.
188
- const throttledContentUpdate = useMemo(() => createStreamingWindowThrottle((params) => {
189
- const { messageId, chunk, stage } = params;
190
- setMessages((prev) => prev.map((m) => {
191
- if (m.id !== messageId)
192
- return m;
193
- const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
194
- if (textBlockIndex === -1) {
195
- return {
196
- ...m,
197
- blocks: [...m.blocks, { type: "text", content: chunk, stage }],
198
- };
199
- }
200
- return {
201
- ...m,
202
- blocks: m.blocks.map((b, idx) => idx === textBlockIndex && b.type === "text"
203
- ? {
204
- ...b,
205
- content: (b.content || "") + chunk,
206
- stage,
207
- }
208
- : b),
209
- };
210
- }));
211
- }, 500), []);
212
- const throttledReasoningUpdate = useMemo(() => createStreamingWindowThrottle((params) => {
213
- const { messageId, chunk, stage } = params;
214
- setMessages((prev) => prev.map((m) => {
215
- if (m.id !== messageId)
216
- return m;
217
- const reasoningBlockIndex = m.blocks.findIndex((b) => b.type === "reasoning");
218
- if (reasoningBlockIndex === -1) {
219
- return {
220
- ...m,
221
- blocks: [
222
- ...m.blocks,
223
- { type: "reasoning", content: chunk, stage },
224
- ],
225
- };
226
- }
227
- return {
228
- ...m,
229
- blocks: m.blocks.map((b, idx) => idx === reasoningBlockIndex && b.type === "reasoning"
230
- ? {
231
- ...b,
232
- content: (b.content || "") + chunk,
233
- stage,
234
- }
235
- : b),
236
- };
237
- }));
238
- }, 500), []);
239
- const throttledToolBlockUpdate = useMemo(() => createToolStreamingThrottle((params) => {
240
- const { messageId, id: toolBlockId, parametersChunk, ...updates } = params;
241
- setMessages((prev) => prev.map((m) => {
242
- if (m.id !== messageId)
243
- return m;
244
- const toolBlockIndex = m.blocks.findIndex((b) => b.type === "tool" && b.id === toolBlockId);
245
- if (toolBlockIndex === -1) {
246
- return {
247
- ...m,
248
- blocks: [
249
- ...m.blocks,
250
- {
251
- type: "tool",
252
- id: toolBlockId,
253
- name: updates.name || "",
254
- stage: updates.stage || "start",
255
- parameters: (updates.parameters || "") + (parametersChunk || ""),
256
- result: updates.result || "",
257
- ...updates,
258
- },
259
- ],
260
- };
261
- }
262
- return {
263
- ...m,
264
- blocks: m.blocks.map((b, idx) => idx === toolBlockIndex && b.type === "tool"
265
- ? {
266
- ...b,
267
- ...updates,
268
- // Streaming carries only the delta; append it to the
269
- // accumulated parameters. start/running/end carry the
270
- // authoritative value and replace wholesale.
271
- parameters: parametersChunk
272
- ? (b.parameters || "") + parametersChunk
273
- : updates.parameters !== undefined
274
- ? updates.parameters
275
- : b.parameters,
276
- }
277
- : b),
278
- };
279
- }));
280
- }, 500), []);
281
- useEffect(() => {
282
- isExpandedRef.current = isExpanded;
283
- if (isExpanded) {
284
- // Cancel pending throttled updates so the frozen expanded view isn't overwritten
285
- throttledContentUpdate.cancel();
286
- throttledReasoningUpdate.cancel();
287
- throttledToolBlockUpdate.cancel();
288
- }
289
- }, [
290
- isExpanded,
291
- throttledContentUpdate,
292
- throttledReasoningUpdate,
293
- throttledToolBlockUpdate,
294
- ]);
295
- const [isLoading, setIsLoading] = useState(false);
296
- const [sessionId, setSessionId] = useState("");
297
- const [isCommandRunning, setIsCommandRunning] = useState(false);
298
- const [isCompacting, setIsCompacting] = useState(false);
299
- const [currentModel, setCurrentModelState] = useState("");
300
- const [configuredModels, setConfiguredModels] = useState([]);
301
- const [queuedMessages, setQueuedMessages] = useState([]);
302
- // MCP State
303
- const [mcpServerStatuses, setMcpServerStatuses] = useState([]);
304
- // Background tasks state
305
- const [backgroundTasks, setBackgroundTasks] = useState([]);
306
- // Workflow runs state
307
- const [workflowRuns, setWorkflowRuns] = useState([]);
308
- // Tasks state
309
- const [tasks, setTasks] = useState([]);
310
- // Command state
311
- const [slashCommands, setSlashCommands] = useState([]);
312
- // Agent definitions (for /agents overlay)
313
- const [agentDefinitions, setAgentDefinitions] = useState([]);
314
- // Permission state
315
- const [permissionMode, setPermissionModeState] = useState(initialPermissionMode ||
316
- (bypassPermissions ? "bypassPermissions" : "default"));
317
- // Confirmation state with queue-based architecture
318
- const [isConfirmationVisible, setIsConfirmationVisible] = useState(false);
319
- const [confirmingTool, setConfirmingTool] = useState();
320
- const [confirmationQueue, setConfirmationQueue] = useState([]);
321
- const [currentConfirmation, setCurrentConfirmation] = useState(null);
322
- // Remount state
323
- const [remountKey, setRemountKey] = useState(0);
324
- // Full terminal clear + remount so Ink's append-only <Static> re-renders.
325
- // Used on structural actions (/clear, /compact, rewind, ctrl-o, forceStatic
326
- // exit) where stale Static output must not linger on screen.
327
- const forceRemount = useCallback(() => {
328
- stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", () => {
329
- setRemountKey((prev) => prev + 1);
330
- });
331
- }, [stdout]);
332
- // Status metadata state
333
- const [workingDirectory, setWorkingDirectory] = useState("");
334
- const agentRef = useRef(null);
335
- // Full-list refresh — one-shot pull from the agent, used only for structural
336
- // changes (compact/clear/rewind/collapse/init). Streaming updates flow through
337
- // the incremental callbacks in initializeAgent below.
338
- const refreshMessages = useCallback(() => {
339
- if (!isExpandedRef.current && agentRef.current) {
340
- const msgs = agentRef.current.messages.map(snapshotMessage);
341
- setMessages(msgs);
342
- setLatestTotalTokens(extractLatestTotalTokens(msgs));
343
- }
344
- }, []);
345
- // Permission confirmation methods with queue support
346
- const showConfirmation = useCallback(async (toolName, toolInput, suggestedPrefix, hidePersistentOption, planContent, permissionMode, warning) => {
347
- return new Promise((resolve, reject) => {
348
- const queueItem = {
349
- toolName,
350
- toolInput,
351
- suggestedPrefix,
352
- hidePersistentOption,
353
- planContent,
354
- permissionMode,
355
- warning,
356
- resolver: resolve,
357
- reject,
358
- };
359
- setConfirmationQueue((prev) => [...prev, queueItem]);
360
- // processNextConfirmation will be called via useEffect
361
- });
362
- }, []);
363
- // Initialize AI manager
364
- const initializeAgent = useCallback(async (restoreSessionIdOverride) => {
365
- const effectiveRestoreSessionId = restoreSessionIdOverride ?? restoreSessionId;
366
- const callbacks = {
367
- // ── Incremental message updates (no full-list pushes) ──────
368
- onUserMessageAdded: () => {
369
- if (isExpandedRef.current || !agentRef.current)
370
- return;
371
- const msgs = agentRef.current.messages;
372
- const last = msgs[msgs.length - 1];
373
- if (!last || last.role !== "user")
374
- return;
375
- setMessages((prev) => prev.some((m) => m.id === last.id)
376
- ? prev
377
- : [...prev, snapshotMessage(last)]);
378
- },
379
- onAssistantMessageAdded: (messageId) => {
380
- if (isExpandedRef.current || !agentRef.current)
381
- return;
382
- const msg = agentRef.current.messages.find((m) => m.id === messageId);
383
- if (!msg)
384
- return;
385
- setMessages((prev) => prev.some((m) => m.id === messageId)
386
- ? prev
387
- : [...prev, snapshotMessage(msg)]);
388
- },
389
- onAssistantContentUpdated: (params) => {
390
- if (isExpandedRef.current)
391
- return;
392
- throttledContentUpdate(params);
393
- },
394
- onAssistantReasoningUpdated: (params) => {
395
- if (isExpandedRef.current)
396
- return;
397
- throttledReasoningUpdate(params);
398
- },
399
- onToolBlockUpdated: (params) => {
400
- if (isExpandedRef.current)
401
- return;
402
- throttledToolBlockUpdate(params);
403
- if (params.stage === "end")
404
- throttledToolBlockUpdate.flush();
405
- },
406
- onErrorBlockAdded: (error) => {
407
- if (isExpandedRef.current)
408
- return;
409
- setMessages((prev) => {
410
- // Append to the last assistant message, or create one if none exists
411
- for (let i = prev.length - 1; i >= 0; i--) {
412
- if (prev[i].role === "assistant") {
413
- return prev.map((m, idx) => idx === i
414
- ? {
415
- ...m,
416
- blocks: [
417
- ...m.blocks,
418
- { type: "error", content: error },
419
- ],
420
- }
421
- : m);
422
- }
423
- }
424
- return [
425
- ...prev,
426
- {
427
- id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
428
- role: "assistant",
429
- timestamp: new Date().toISOString(),
430
- blocks: [{ type: "error", content: error }],
431
- },
432
- ];
433
- });
434
- },
435
- onAddBangMessage: (command, messageId) => {
436
- if (isExpandedRef.current)
437
- return;
438
- setMessages((prev) => prev.some((m) => m.id === messageId)
439
- ? prev
440
- : [
441
- ...prev,
442
- {
443
- id: messageId,
444
- role: "user",
445
- timestamp: new Date().toISOString(),
446
- blocks: [
447
- {
448
- type: "bang",
449
- command,
450
- output: "",
451
- stage: "running",
452
- exitCode: null,
453
- },
454
- ],
455
- },
456
- ]);
457
- },
458
- onUpdateBangMessage: (command, output, messageId) => {
459
- if (isExpandedRef.current)
460
- return;
461
- setMessages((prev) => prev.map((m) => m.id === messageId
462
- ? {
463
- ...m,
464
- blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
465
- ? { ...b, command, output }
466
- : b),
467
- }
468
- : m));
469
- },
470
- onCompleteBangMessage: (command, exitCode, messageId, output) => {
471
- if (isExpandedRef.current)
472
- return;
473
- setMessages((prev) => prev.map((m) => m.id === messageId
474
- ? {
475
- ...m,
476
- blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
477
- ? {
478
- ...b,
479
- command,
480
- exitCode,
481
- stage: "end",
482
- ...(output !== undefined ? { output } : {}),
483
- }
484
- : b),
485
- }
486
- : m));
487
- },
488
- onLatestTotalTokensChange: (tokens) => {
489
- setLatestTotalTokens(tokens);
490
- },
491
- onMcpServersChange: (servers) => {
492
- setMcpServerStatuses([...servers]);
493
- },
494
- onSessionIdChange: (sessionId) => {
495
- setSessionId(sessionId);
496
- },
497
- onCompactionStateChange: (isCompactingState) => {
498
- setIsCompacting(isCompactingState);
499
- },
500
- onBackgroundTasksChange: async (tasks) => {
501
- setBackgroundTasks([...tasks]);
502
- // Also refresh workflow runs since workflows are background tasks
503
- const runs = await agentRef.current?.getWorkflowRuns();
504
- if (runs)
505
- setWorkflowRuns([...runs]);
506
- },
507
- onTasksChange: (newTasks) => {
508
- setTasks((prev) => {
509
- if (prev.length === newTasks.length &&
510
- prev.every((t, i) => t.id === newTasks[i].id && t.status === newTasks[i].status)) {
511
- return prev;
512
- }
513
- return [...newTasks];
514
- });
515
- },
516
- onPermissionModeChange: (mode) => {
517
- setPermissionModeState(mode);
518
- },
519
- onModelChange: (model) => {
520
- setCurrentModelState(model);
521
- },
522
- onConfiguredModelsChange: (models) => {
523
- setConfiguredModels(models);
524
- },
525
- onLoadingChange: (loading) => {
526
- setIsLoading(loading);
527
- },
528
- onCommandRunningChange: (running) => {
529
- setIsCommandRunning(running);
530
- },
531
- onQueuedMessagesChange: (messages) => {
532
- setQueuedMessages([...messages]);
533
- },
534
- };
535
- try {
536
- // Create the permission callback inside the try block to access showConfirmation
537
- const permissionCallback = async (context) => {
538
- try {
539
- return await showConfirmation(context.toolName, context.toolInput, context.suggestedPrefix, context.hidePersistentOption, context.planContent, context.permissionMode, context.warning);
540
- }
541
- catch {
542
- // If confirmation was cancelled or failed, deny the operation
543
- return {
544
- behavior: "deny",
545
- message: OPERATION_CANCELLED_BY_USER,
546
- };
547
- }
548
- };
549
- const agent = await Agent.create({
550
- callbacks,
551
- restoreSessionId: effectiveRestoreSessionId,
552
- continueLastSession,
553
- logger,
554
- permissionMode: initialPermissionMode ||
555
- (bypassPermissions ? "bypassPermissions" : undefined),
556
- canUseTool: permissionCallback,
557
- stream: true,
558
- plugins: pluginDirs?.map((path) => ({ type: "local", path })),
559
- additionalDirectories,
560
- tools,
561
- allowedTools,
562
- disallowedTools,
563
- workdir,
564
- worktreeName: worktreeSession?.name,
565
- isNewWorktree: worktreeSession?.isNew,
566
- model,
567
- mcpServers,
568
- });
569
- agentRef.current = agent;
570
- // Inject worktree session into this agent's own DI container so system
571
- // prompts and permission checks reflect the CLI -w worktree. This state is
572
- // per-session, not process-global (see docs/specs/multi-agent/worktree.md FR-042).
573
- if (worktreeSession) {
574
- const session = {
575
- originalCwd: originalCwd ?? worktreeSession.repoRoot,
576
- worktreePath: worktreeSession.path,
577
- worktreeBranch: worktreeSession.branch,
578
- worktreeName: worktreeSession.name,
579
- isNew: worktreeSession.isNew,
580
- repoRoot: worktreeSession.repoRoot,
581
- };
582
- agent.setWorktreeSession(session);
583
- }
584
- // Get initial state — snapshot the SDK messages (never hold live
585
- // references; see snapshotMessage)
586
- setSessionId(agent.sessionId);
587
- setMessages(agent.messages.map(snapshotMessage));
588
- setIsLoading(agent.isLoading);
589
- setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
590
- setIsCommandRunning(agent.isCommandRunning);
591
- setIsCompacting(agent.isCompacting);
592
- setPermissionModeState(agent.getPermissionMode());
593
- setWorkingDirectory(agent.workingDirectory);
594
- setCurrentModelState(agent.getModelConfig().model || "");
595
- setConfiguredModels(agent.getConfiguredModels());
596
- setMaxInputTokens(agent.getMaxInputTokens());
597
- // Get initial MCP servers state
598
- const initialMcpServers = agent.getMcpServers?.() || [];
599
- setMcpServerStatuses(initialMcpServers);
600
- // Get initial commands
601
- const agentSlashCommands = agent.getSlashCommands?.() || [];
602
- setSlashCommands(agentSlashCommands);
603
- // Get initial agent definitions
604
- const initialAgentDefinitions = agent.getSubagentConfigurations?.() || [];
605
- setAgentDefinitions(initialAgentDefinitions);
606
- }
607
- catch (error) {
608
- console.error("Failed to initialize AI manager:", error);
609
- }
610
- }, [
611
- restoreSessionId,
612
- continueLastSession,
613
- bypassPermissions,
614
- showConfirmation,
615
- pluginDirs,
616
- additionalDirectories,
617
- tools,
618
- allowedTools,
619
- disallowedTools,
620
- workdir,
621
- worktreeSession,
622
- originalCwd,
623
- model,
624
- initialPermissionMode,
625
- throttledContentUpdate,
626
- throttledReasoningUpdate,
627
- throttledToolBlockUpdate,
628
- mcpServers,
629
- ]);
630
- // Recreate agent (e.g. after plugin install) — destroys current agent and reinitializes
631
- const recreateAgent = useCallback(() => {
632
- const currentSessionId = agentRef.current?.sessionId;
633
- if (agentRef.current) {
634
- try {
635
- agentRef.current.destroy();
636
- }
637
- catch {
638
- // Ignore destroy errors
639
- }
640
- }
641
- agentRef.current = null;
642
- setMessages([]);
643
- setMcpServerStatuses([]);
644
- setSlashCommands([]);
645
- setAgentDefinitions([]);
646
- setSessionId("");
647
- setIsLoading(false);
648
- setLatestTotalTokens(0);
649
- setMaxInputTokens(200000);
650
- setIsCommandRunning(false);
651
- setIsCompacting(false);
652
- if (currentSessionId) {
653
- initializeAgent(currentSessionId);
654
- }
655
- }, [initializeAgent]);
656
- // Run initial agent creation
657
- useEffect(() => {
658
- initializeAgent();
659
- }, [initializeAgent]);
660
- // Cleanup on unmount
661
- useEffect(() => {
662
- return () => {
663
- throttledContentUpdate.cancel();
664
- throttledReasoningUpdate.cancel();
665
- throttledToolBlockUpdate.cancel();
666
- if (agentRef.current) {
667
- try {
668
- // Display usage summary before cleanup
669
- const usages = agentRef.current.usages;
670
- const sessionFilePath = agentRef.current.sessionFilePath;
671
- displayUsageSummary(usages, sessionFilePath);
672
- }
673
- catch {
674
- // Silently ignore usage summary errors during cleanup
675
- }
676
- agentRef.current.destroy();
677
- }
678
- };
679
- }, [
680
- throttledContentUpdate,
681
- throttledReasoningUpdate,
682
- throttledToolBlockUpdate,
683
- ]);
684
- // Trigger WorktreeRemove hook BEFORE agent destruction
685
- const triggerWorktreeRemoveHook = useCallback(async (worktreePath) => {
686
- await agentRef.current?.triggerWorktreeRemoveHook(worktreePath);
687
- }, []);
688
- // Send message function (including judgment logic)
689
- const sendMessage = useCallback(async (content, images, longTextMap) => {
690
- // Check if there's content to send (text content or image attachments)
691
- const hasTextContent = content.trim();
692
- const hasImageAttachments = images && images.length > 0;
693
- if (!hasTextContent && !hasImageAttachments)
694
- return;
695
- try {
696
- const expandedContent = longTextMap
697
- ? expandLongTextPlaceholders(content, longTextMap)
698
- : content;
699
- // Handle bash mode - check if it's a bash command (starts with ! and only one line)
700
- if (expandedContent.startsWith("!") &&
701
- !expandedContent.includes("\n") &&
702
- !hasImageAttachments) {
703
- const command = expandedContent.substring(1).trim();
704
- if (command) {
705
- await agentRef.current?.bang(command);
706
- return;
707
- }
708
- }
709
- try {
710
- await agentRef.current?.sendMessage(expandedContent, images);
711
- }
712
- catch (error) {
713
- console.error("Failed to send message:", error);
714
- }
715
- }
716
- catch (error) {
717
- console.error("Failed to send message:", error);
718
- }
719
- }, []);
720
- const askBtw = useCallback(async (question, abortSignal, onContent) => {
721
- if (!agentRef.current) {
722
- throw new Error("Agent not initialized");
723
- }
724
- return await agentRef.current.askBtw(question, abortSignal, onContent);
725
- }, []);
726
- const clearMessages = useCallback(async () => {
727
- await agentRef.current?.clearMessages();
728
- refreshMessages();
729
- forceRemount();
730
- }, [refreshMessages, forceRemount]);
731
- const compact = useCallback(async (instructions) => {
732
- await agentRef.current?.compact(instructions);
733
- refreshMessages();
734
- forceRemount();
735
- }, [refreshMessages, forceRemount]);
736
- // /add-dir: add a directory to the session Safe Zone (session-level, with
737
- // optional --remember persistence). Bare command shows usage + current list.
738
- const addDir = useCallback(async (args) => {
739
- const agent = agentRef.current;
740
- if (!agent)
741
- return;
742
- // Show the result as a UI-only assistant text message (same display path
743
- // as error blocks; not persisted to session history).
744
- const showResult = (content) => {
745
- setMessages((prev) => [
746
- ...prev,
747
- {
748
- id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
749
- role: "assistant",
750
- timestamp: new Date().toISOString(),
751
- blocks: [{ type: "text", content, stage: "end" }],
752
- },
753
- ]);
754
- };
755
- const rawArgs = (args ?? "").trim();
756
- if (!rawArgs) {
757
- const dirs = agent.getAdditionalDirectories();
758
- const list = dirs.length > 0
759
- ? dirs.map((dir) => ` - ${dir}`).join("\n")
760
- : " (none)";
761
- showResult(`Usage: /add-dir <path> [--remember]\n\nAdditional working directories:\n${list}`);
762
- return;
763
- }
764
- const remember = rawArgs.includes("--remember");
765
- const dir = rawArgs.replace("--remember", "").trim();
766
- if (!dir) {
767
- showResult("Usage: /add-dir <path> [--remember]");
768
- return;
769
- }
770
- await agent.addAdditionalDirectory(dir, { remember });
771
- showResult(`Added ${dir} to the session Safe Zone${remember ? " (remembered)" : ""}.`);
772
- }, []);
773
- // Unified interrupt method, interrupt both AI messages and command execution
774
- const abortMessage = useCallback(() => {
775
- agentRef.current?.abortMessage();
776
- }, []);
777
- const recallQueuedMessage = useCallback(() => {
778
- return agentRef.current?.recallQueuedMessage() ?? null;
779
- }, []);
780
- const removeQueuedMessageById = useCallback((id) => {
781
- return agentRef.current?.removeQueuedMessageById(id) ?? false;
782
- }, []);
783
- // Permission management methods
784
- const setPermissionMode = useCallback((mode) => {
785
- setPermissionModeState((prev) => {
786
- if (prev === mode)
787
- return prev;
788
- if (agentRef.current && agentRef.current.getPermissionMode() !== mode) {
789
- agentRef.current.setPermissionMode(mode);
790
- }
791
- return mode;
792
- });
793
- }, []);
794
- // MCP management methods - delegate to Agent
795
- const connectMcpServer = useCallback(async (serverName) => {
796
- return (await agentRef.current?.connectMcpServer(serverName)) ?? false;
797
- }, []);
798
- const disconnectMcpServer = useCallback(async (serverName) => {
799
- return (await agentRef.current?.disconnectMcpServer(serverName)) ?? false;
800
- }, []);
801
- // Background task management methods - delegate to Agent
802
- const getBackgroundTaskOutput = useCallback((taskId) => {
803
- if (!agentRef.current)
804
- return null;
805
- return agentRef.current.getBackgroundTaskOutput(taskId);
806
- }, []);
807
- const stopBackgroundTask = useCallback((taskId) => {
808
- if (!agentRef.current)
809
- return false;
810
- return agentRef.current.stopBackgroundTask(taskId);
811
- }, []);
812
- const stopWorkflowRun = useCallback((runId) => {
813
- agentRef.current?.stopWorkflowRun(runId);
814
- }, []);
815
- const hasSlashCommand = useCallback((commandId) => {
816
- if (!agentRef.current)
817
- return false;
818
- return agentRef.current.hasSlashCommand(commandId);
819
- }, []);
820
- // Queue processing helper
821
- const processNextConfirmation = useCallback(() => {
822
- if (confirmationQueue.length > 0 && !isConfirmationVisible) {
823
- const next = confirmationQueue[0];
824
- setCurrentConfirmation(next);
825
- setConfirmingTool({
826
- name: next.toolName,
827
- input: next.toolInput,
828
- suggestedPrefix: next.suggestedPrefix,
829
- hidePersistentOption: next.hidePersistentOption,
830
- planContent: next.planContent,
831
- permissionMode: next.permissionMode,
832
- warning: next.warning,
833
- });
834
- setIsConfirmationVisible(true);
835
- setConfirmationQueue((prev) => prev.slice(1));
836
- }
837
- }, [confirmationQueue, isConfirmationVisible]);
838
- // Process queue when queue changes or confirmation is hidden
839
- useEffect(() => {
840
- processNextConfirmation();
841
- }, [processNextConfirmation]);
842
- const hideConfirmation = useCallback(() => {
843
- setIsConfirmationVisible(false);
844
- setConfirmingTool(undefined);
845
- setCurrentConfirmation(null);
846
- }, []);
847
- const handleConfirmationDecision = useCallback((decision) => {
848
- if (currentConfirmation) {
849
- currentConfirmation.resolver(decision);
850
- }
851
- hideConfirmation();
852
- }, [currentConfirmation, hideConfirmation]);
853
- const handleConfirmationCancel = useCallback(() => {
854
- if (currentConfirmation) {
855
- currentConfirmation.reject();
856
- }
857
- agentRef.current?.abortMessage();
858
- hideConfirmation();
859
- }, [currentConfirmation, hideConfirmation]);
860
- const backgroundCurrentTask = useCallback(() => {
861
- agentRef.current?.backgroundCurrentTask();
862
- }, []);
863
- const handleRewindSelect = useCallback(async (index) => {
864
- if (agentRef.current) {
865
- try {
866
- await agentRef.current.truncateHistory(index);
867
- refreshMessages();
868
- forceRemount();
869
- }
870
- catch (error) {
871
- logger.error("Failed to rewind:", error);
872
- }
873
- }
874
- }, [forceRemount, refreshMessages]);
875
- const getFullMessageThread = useCallback(async () => {
876
- if (agentRef.current) {
877
- return await agentRef.current.getFullMessageThread();
878
- }
879
- return { messages: [], sessionIds: [] };
880
- }, []);
881
- const getGatewayConfig = useCallback(() => {
882
- if (!agentRef.current) {
883
- return { baseURL: "" };
884
- }
885
- return agentRef.current.getGatewayConfig();
886
- }, []);
887
- const getModelConfig = useCallback(() => {
888
- if (!agentRef.current) {
889
- return { model: "", fastModel: "" };
890
- }
891
- return agentRef.current.getModelConfig();
892
- }, []);
893
- const getConfiguredModels = useCallback(() => {
894
- if (!agentRef.current) {
895
- return [];
896
- }
897
- return agentRef.current.getConfiguredModels();
898
- }, []);
899
- const setModel = useCallback((model) => {
900
- if (agentRef.current) {
901
- agentRef.current.setModel(model);
902
- setCurrentModelState(model);
903
- }
904
- }, []);
905
- // Listen for Ctrl+O hotkey to toggle collapse/expand state and ESC to cancel confirmation
906
- useInput((input, key) => {
907
- if (key.ctrl && input === "o") {
908
- // Use ref to get the current value to avoid stale closure
909
- const nextExpanded = !isExpandedRef.current;
910
- setIsExpanded(nextExpanded);
911
- isExpandedRef.current = nextExpanded;
912
- if (nextExpanded) {
913
- // Transitioning to EXPANDED: Freeze the current view
914
- // Incremental updates are skipped while expanded (isExpandedRef guard)
915
- }
916
- else {
917
- // Transitioning to COLLAPSED: Restore from agent's actual state
918
- refreshMessages();
919
- }
920
- // Force remount to ensure Static items re-render
921
- forceRemount();
922
- }
923
- if (key.ctrl && input === "t") {
924
- setIsTaskListVisible((prev) => !prev);
925
- }
926
- // Handle ESC key to cancel confirmation
927
- if (key.escape && isConfirmationVisible) {
928
- handleConfirmationCancel();
929
- }
930
- });
931
- const contextValue = {
932
- messages,
933
- isLoading,
934
- isCommandRunning,
935
- isExpanded,
936
- isTaskListVisible,
937
- setIsTaskListVisible,
938
- isBtwActive,
939
- setIsBtwActive,
940
- queuedMessages,
941
- sessionId,
942
- sendMessage,
943
- askBtw,
944
- clearMessages,
945
- compact,
946
- addDir,
947
- abortMessage,
948
- recallQueuedMessage,
949
- removeQueuedMessageById,
950
- latestTotalTokens,
951
- maxInputTokens,
952
- currentModel,
953
- configuredModels,
954
- getConfiguredModels,
955
- setModel,
956
- isCompacting,
957
- mcpServers: mcpServerStatuses,
958
- connectMcpServer,
959
- disconnectMcpServer,
960
- backgroundTasks,
961
- workflowRuns,
962
- stopWorkflowRun,
963
- tasks,
964
- getBackgroundTaskOutput,
965
- stopBackgroundTask,
966
- slashCommands,
967
- hasSlashCommand,
968
- agentDefinitions,
969
- permissionMode,
970
- setPermissionMode,
971
- isConfirmationVisible,
972
- hasPendingConfirmations: confirmationQueue.length > 0,
973
- confirmingTool,
974
- showConfirmation,
975
- hideConfirmation,
976
- handleConfirmationDecision,
977
- handleConfirmationCancel,
978
- backgroundCurrentTask,
979
- remountKey,
980
- forceRemount,
981
- handleRewindSelect,
982
- getFullMessageThread,
983
- getGatewayConfig,
984
- getModelConfig,
985
- workingDirectory,
986
- version,
987
- workdir,
988
- recreateAgent,
989
- triggerWorktreeRemoveHook,
990
- };
991
- return (_jsx(ChatContext.Provider, { value: contextValue, children: children }));
992
- };