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.
Files changed (248) hide show
  1. package/bin/wave-code.js +8 -2
  2. package/dist/bundle/wave.mjs +3231 -0
  3. package/package.json +24 -25
  4. package/src/cli.tsx +6 -1
  5. package/src/commands/update.ts +2 -7
  6. package/src/components/App.tsx +3 -7
  7. package/src/components/BtwDisplay.tsx +10 -1
  8. package/src/components/ChatInterface.tsx +21 -52
  9. package/src/components/ConfirmationDetails.tsx +144 -37
  10. package/src/components/DiffDisplay.tsx +335 -301
  11. package/src/components/InputBox.tsx +17 -2
  12. package/src/components/LoadingIndicator.tsx +16 -2
  13. package/src/components/MessageBlockItem.tsx +2 -4
  14. package/src/components/MessageList.tsx +75 -14
  15. package/src/components/ReasoningDisplay.tsx +2 -4
  16. package/src/components/SessionSelector.tsx +82 -11
  17. package/src/components/SkillsManager.tsx +312 -0
  18. package/src/components/StatusCommand.tsx +2 -9
  19. package/src/components/ToolDisplay.tsx +2 -4
  20. package/src/constants/commands.ts +6 -0
  21. package/src/contexts/useChat.tsx +245 -301
  22. package/src/daemon/commands.ts +57 -27
  23. package/src/daemon/jsonRpcClient.ts +1 -1
  24. package/src/daemon/socketClient.ts +2 -2
  25. package/src/hooks/useInputManager.ts +7 -1
  26. package/src/index.ts +57 -10
  27. package/src/managers/inputHandlers.ts +2 -0
  28. package/src/managers/inputReducer.ts +195 -104
  29. package/src/print-cli.ts +25 -17
  30. package/src/reducers/selectorReducer.ts +10 -0
  31. package/src/reducers/skillsManagerReducer.ts +91 -0
  32. package/src/session-selector-cli.tsx +269 -28
  33. package/src/stdio/agentBridge.ts +30 -21
  34. package/src/stdio/daemonServer.ts +3 -1
  35. package/src/stdio/protocol.ts +2 -0
  36. package/src/utils/clipboard.ts +21 -0
  37. package/src/utils/highlightUtils.ts +46 -1
  38. package/src/utils/readPackageJson.ts +29 -0
  39. package/src/utils/streamingText.ts +11 -0
  40. package/src/utils/worktree.ts +191 -43
  41. package/dist/cli.d.ts +0 -6
  42. package/dist/cli.js +0 -53
  43. package/dist/commands/plugin/disable.d.ts +0 -5
  44. package/dist/commands/plugin/disable.js +0 -15
  45. package/dist/commands/plugin/enable.d.ts +0 -5
  46. package/dist/commands/plugin/enable.js +0 -15
  47. package/dist/commands/plugin/install.d.ts +0 -5
  48. package/dist/commands/plugin/install.js +0 -21
  49. package/dist/commands/plugin/list.d.ts +0 -1
  50. package/dist/commands/plugin/list.js +0 -31
  51. package/dist/commands/plugin/marketplace.d.ts +0 -10
  52. package/dist/commands/plugin/marketplace.js +0 -90
  53. package/dist/commands/plugin/uninstall.d.ts +0 -3
  54. package/dist/commands/plugin/uninstall.js +0 -16
  55. package/dist/commands/plugin/update.d.ts +0 -3
  56. package/dist/commands/plugin/update.js +0 -15
  57. package/dist/commands/update.d.ts +0 -1
  58. package/dist/commands/update.js +0 -97
  59. package/dist/components/AgentsManager.d.ts +0 -7
  60. package/dist/components/AgentsManager.js +0 -109
  61. package/dist/components/App.d.ts +0 -9
  62. package/dist/components/App.js +0 -76
  63. package/dist/components/BackgroundTaskManager.d.ts +0 -5
  64. package/dist/components/BackgroundTaskManager.js +0 -99
  65. package/dist/components/BangDisplay.d.ts +0 -8
  66. package/dist/components/BangDisplay.js +0 -17
  67. package/dist/components/BtwDisplay.d.ts +0 -7
  68. package/dist/components/BtwDisplay.js +0 -19
  69. package/dist/components/ChatInterface.d.ts +0 -2
  70. package/dist/components/ChatInterface.js +0 -61
  71. package/dist/components/CommandSelector.d.ts +0 -10
  72. package/dist/components/CommandSelector.js +0 -66
  73. package/dist/components/CompactDisplay.d.ts +0 -8
  74. package/dist/components/CompactDisplay.js +0 -13
  75. package/dist/components/ConfirmationDetails.d.ts +0 -9
  76. package/dist/components/ConfirmationDetails.js +0 -45
  77. package/dist/components/ConfirmationSelector.d.ts +0 -13
  78. package/dist/components/ConfirmationSelector.js +0 -127
  79. package/dist/components/DiffDisplay.d.ts +0 -8
  80. package/dist/components/DiffDisplay.js +0 -201
  81. package/dist/components/DiscoverView.d.ts +0 -2
  82. package/dist/components/DiscoverView.js +0 -37
  83. package/dist/components/FileSelector.d.ts +0 -11
  84. package/dist/components/FileSelector.js +0 -50
  85. package/dist/components/HelpView.d.ts +0 -7
  86. package/dist/components/HelpView.js +0 -74
  87. package/dist/components/HistorySearch.d.ts +0 -8
  88. package/dist/components/HistorySearch.js +0 -66
  89. package/dist/components/InputBox.d.ts +0 -24
  90. package/dist/components/InputBox.js +0 -149
  91. package/dist/components/InstalledView.d.ts +0 -2
  92. package/dist/components/InstalledView.js +0 -45
  93. package/dist/components/LoadingIndicator.d.ts +0 -10
  94. package/dist/components/LoadingIndicator.js +0 -6
  95. package/dist/components/LoginCommand.d.ts +0 -5
  96. package/dist/components/LoginCommand.js +0 -153
  97. package/dist/components/Markdown.d.ts +0 -5
  98. package/dist/components/Markdown.js +0 -127
  99. package/dist/components/MarketplaceAddForm.d.ts +0 -2
  100. package/dist/components/MarketplaceAddForm.js +0 -55
  101. package/dist/components/MarketplaceDetail.d.ts +0 -2
  102. package/dist/components/MarketplaceDetail.js +0 -74
  103. package/dist/components/MarketplaceList.d.ts +0 -8
  104. package/dist/components/MarketplaceList.js +0 -17
  105. package/dist/components/MarketplaceView.d.ts +0 -2
  106. package/dist/components/MarketplaceView.js +0 -41
  107. package/dist/components/McpManager.d.ts +0 -9
  108. package/dist/components/McpManager.js +0 -88
  109. package/dist/components/MessageBlockItem.d.ts +0 -8
  110. package/dist/components/MessageBlockItem.js +0 -15
  111. package/dist/components/MessageList.d.ts +0 -10
  112. package/dist/components/MessageList.js +0 -80
  113. package/dist/components/ModelSelector.d.ts +0 -8
  114. package/dist/components/ModelSelector.js +0 -45
  115. package/dist/components/Notifications.d.ts +0 -7
  116. package/dist/components/Notifications.js +0 -9
  117. package/dist/components/PlanDisplay.d.ts +0 -7
  118. package/dist/components/PlanDisplay.js +0 -6
  119. package/dist/components/PluginDetail.d.ts +0 -2
  120. package/dist/components/PluginDetail.js +0 -87
  121. package/dist/components/PluginList.d.ts +0 -13
  122. package/dist/components/PluginList.js +0 -15
  123. package/dist/components/PluginManagerShell.d.ts +0 -6
  124. package/dist/components/PluginManagerShell.js +0 -92
  125. package/dist/components/PluginManagerTypes.d.ts +0 -35
  126. package/dist/components/PluginManagerTypes.js +0 -1
  127. package/dist/components/QueuedMessageList.d.ts +0 -2
  128. package/dist/components/QueuedMessageList.js +0 -18
  129. package/dist/components/ReasoningDisplay.d.ts +0 -8
  130. package/dist/components/ReasoningDisplay.js +0 -13
  131. package/dist/components/RewindCommand.d.ts +0 -12
  132. package/dist/components/RewindCommand.js +0 -78
  133. package/dist/components/SessionSelector.d.ts +0 -12
  134. package/dist/components/SessionSelector.js +0 -47
  135. package/dist/components/StatusCommand.d.ts +0 -5
  136. package/dist/components/StatusCommand.js +0 -28
  137. package/dist/components/StatusLine.d.ts +0 -6
  138. package/dist/components/StatusLine.js +0 -9
  139. package/dist/components/TaskList.d.ts +0 -5
  140. package/dist/components/TaskList.js +0 -197
  141. package/dist/components/TaskNotificationMessage.d.ts +0 -5
  142. package/dist/components/TaskNotificationMessage.js +0 -12
  143. package/dist/components/ToolDisplay.d.ts +0 -9
  144. package/dist/components/ToolDisplay.js +0 -55
  145. package/dist/components/WorkflowManager.d.ts +0 -5
  146. package/dist/components/WorkflowManager.js +0 -102
  147. package/dist/components/WorktreeExitPrompt.d.ts +0 -12
  148. package/dist/components/WorktreeExitPrompt.js +0 -35
  149. package/dist/constants/commands.d.ts +0 -2
  150. package/dist/constants/commands.js +0 -92
  151. package/dist/contexts/PluginManagerContext.d.ts +0 -3
  152. package/dist/contexts/PluginManagerContext.js +0 -9
  153. package/dist/contexts/useAppConfig.d.ts +0 -10
  154. package/dist/contexts/useAppConfig.js +0 -13
  155. package/dist/contexts/useChat.d.ts +0 -101
  156. package/dist/contexts/useChat.js +0 -993
  157. package/dist/contracts/status.d.ts +0 -7
  158. package/dist/contracts/status.js +0 -1
  159. package/dist/daemon/commands.d.ts +0 -49
  160. package/dist/daemon/commands.js +0 -341
  161. package/dist/daemon/jsonRpcClient.d.ts +0 -38
  162. package/dist/daemon/jsonRpcClient.js +0 -129
  163. package/dist/daemon/socketClient.d.ts +0 -13
  164. package/dist/daemon/socketClient.js +0 -26
  165. package/dist/daemon-cli.d.ts +0 -10
  166. package/dist/daemon-cli.js +0 -15
  167. package/dist/hooks/useInputManager.d.ts +0 -75
  168. package/dist/hooks/useInputManager.js +0 -583
  169. package/dist/hooks/usePluginManager.d.ts +0 -4
  170. package/dist/hooks/usePluginManager.js +0 -265
  171. package/dist/hooks/useTasks.d.ts +0 -1
  172. package/dist/hooks/useTasks.js +0 -5
  173. package/dist/index.d.ts +0 -4
  174. package/dist/index.js +0 -447
  175. package/dist/managers/inputHandlers.d.ts +0 -30
  176. package/dist/managers/inputHandlers.js +0 -625
  177. package/dist/managers/inputReducer.d.ts +0 -271
  178. package/dist/managers/inputReducer.js +0 -1121
  179. package/dist/plugin-manager-cli.d.ts +0 -5
  180. package/dist/plugin-manager-cli.js +0 -12
  181. package/dist/print-cli.d.ts +0 -10
  182. package/dist/print-cli.js +0 -209
  183. package/dist/reducers/agentsManagerReducer.d.ts +0 -26
  184. package/dist/reducers/agentsManagerReducer.js +0 -54
  185. package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
  186. package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
  187. package/dist/reducers/confirmationReducer.d.ts +0 -37
  188. package/dist/reducers/confirmationReducer.js +0 -193
  189. package/dist/reducers/helpSelectorReducer.d.ts +0 -25
  190. package/dist/reducers/helpSelectorReducer.js +0 -52
  191. package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
  192. package/dist/reducers/marketplaceAddFormReducer.js +0 -58
  193. package/dist/reducers/mcpManagerReducer.d.ts +0 -37
  194. package/dist/reducers/mcpManagerReducer.js +0 -74
  195. package/dist/reducers/pluginDetailReducer.d.ts +0 -32
  196. package/dist/reducers/pluginDetailReducer.js +0 -70
  197. package/dist/reducers/questionReducer.d.ts +0 -82
  198. package/dist/reducers/questionReducer.js +0 -375
  199. package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
  200. package/dist/reducers/rewindSelectorReducer.js +0 -37
  201. package/dist/reducers/selectorReducer.d.ts +0 -23
  202. package/dist/reducers/selectorReducer.js +0 -42
  203. package/dist/reducers/workflowManagerReducer.d.ts +0 -40
  204. package/dist/reducers/workflowManagerReducer.js +0 -100
  205. package/dist/session-selector-cli.d.ts +0 -3
  206. package/dist/session-selector-cli.js +0 -25
  207. package/dist/stdio/agentBridge.d.ts +0 -139
  208. package/dist/stdio/agentBridge.js +0 -999
  209. package/dist/stdio/daemonServer.d.ts +0 -67
  210. package/dist/stdio/daemonServer.js +0 -191
  211. package/dist/stdio/index.d.ts +0 -5
  212. package/dist/stdio/index.js +0 -5
  213. package/dist/stdio/jsonRpcConnection.d.ts +0 -39
  214. package/dist/stdio/jsonRpcConnection.js +0 -135
  215. package/dist/stdio/protocol.d.ts +0 -41
  216. package/dist/stdio/protocol.js +0 -26
  217. package/dist/stdio/stdioServer.d.ts +0 -36
  218. package/dist/stdio/stdioServer.js +0 -51
  219. package/dist/stdio-cli.d.ts +0 -8
  220. package/dist/stdio-cli.js +0 -15
  221. package/dist/types.d.ts +0 -20
  222. package/dist/types.js +0 -1
  223. package/dist/utils/bracketedPaste.d.ts +0 -39
  224. package/dist/utils/bracketedPaste.js +0 -122
  225. package/dist/utils/clipboard.d.ts +0 -21
  226. package/dist/utils/clipboard.js +0 -347
  227. package/dist/utils/constants.d.ts +0 -16
  228. package/dist/utils/constants.js +0 -18
  229. package/dist/utils/highlightUtils.d.ts +0 -1
  230. package/dist/utils/highlightUtils.js +0 -93
  231. package/dist/utils/inputUtils.d.ts +0 -17
  232. package/dist/utils/inputUtils.js +0 -67
  233. package/dist/utils/logger.d.ts +0 -71
  234. package/dist/utils/logger.js +0 -259
  235. package/dist/utils/markdownTable.d.ts +0 -34
  236. package/dist/utils/markdownTable.js +0 -302
  237. package/dist/utils/rewindCheckpoints.d.ts +0 -8
  238. package/dist/utils/rewindCheckpoints.js +0 -17
  239. package/dist/utils/throttle.d.ts +0 -14
  240. package/dist/utils/throttle.js +0 -55
  241. package/dist/utils/toolParameterTransforms.d.ts +0 -18
  242. package/dist/utils/toolParameterTransforms.js +0 -73
  243. package/dist/utils/usageSummary.d.ts +0 -28
  244. package/dist/utils/usageSummary.js +0 -121
  245. package/dist/utils/version.d.ts +0 -1
  246. package/dist/utils/version.js +0 -4
  247. package/dist/utils/worktree.d.ts +0 -35
  248. package/dist/utils/worktree.js +0 -219
@@ -1,993 +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 message only if it is an assistant message
411
- // (the current turn's in-flight reply). If the last message is a
412
- // user message, the error belongs BELOW it — create a new
413
- // assistant message instead of polluting the stale assistant from
414
- // an earlier turn (which surfaced the error above the latest
415
- // user message and accumulated it there).
416
- const last = prev[prev.length - 1];
417
- if (last && last.role === "assistant") {
418
- return prev.map((m, idx) => idx === prev.length - 1
419
- ? {
420
- ...m,
421
- blocks: [...m.blocks, { type: "error", content: error }],
422
- }
423
- : m);
424
- }
425
- return [
426
- ...prev,
427
- {
428
- id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
429
- role: "assistant",
430
- timestamp: new Date().toISOString(),
431
- blocks: [{ type: "error", content: error }],
432
- },
433
- ];
434
- });
435
- },
436
- onAddBangMessage: (command, messageId) => {
437
- if (isExpandedRef.current)
438
- return;
439
- setMessages((prev) => prev.some((m) => m.id === messageId)
440
- ? prev
441
- : [
442
- ...prev,
443
- {
444
- id: messageId,
445
- role: "user",
446
- timestamp: new Date().toISOString(),
447
- blocks: [
448
- {
449
- type: "bang",
450
- command,
451
- output: "",
452
- stage: "running",
453
- exitCode: null,
454
- },
455
- ],
456
- },
457
- ]);
458
- },
459
- onUpdateBangMessage: (command, output, messageId) => {
460
- if (isExpandedRef.current)
461
- return;
462
- setMessages((prev) => prev.map((m) => m.id === messageId
463
- ? {
464
- ...m,
465
- blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
466
- ? { ...b, command, output }
467
- : b),
468
- }
469
- : m));
470
- },
471
- onCompleteBangMessage: (command, exitCode, messageId, output) => {
472
- if (isExpandedRef.current)
473
- return;
474
- setMessages((prev) => prev.map((m) => m.id === messageId
475
- ? {
476
- ...m,
477
- blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
478
- ? {
479
- ...b,
480
- command,
481
- exitCode,
482
- stage: "end",
483
- ...(output !== undefined ? { output } : {}),
484
- }
485
- : b),
486
- }
487
- : m));
488
- },
489
- onLatestTotalTokensChange: (tokens) => {
490
- setLatestTotalTokens(tokens);
491
- },
492
- onMcpServersChange: (servers) => {
493
- setMcpServerStatuses([...servers]);
494
- },
495
- onSessionIdChange: (sessionId) => {
496
- setSessionId(sessionId);
497
- },
498
- onCompactionStateChange: (isCompactingState) => {
499
- setIsCompacting(isCompactingState);
500
- },
501
- onBackgroundTasksChange: async (tasks) => {
502
- setBackgroundTasks([...tasks]);
503
- // Also refresh workflow runs since workflows are background tasks
504
- const runs = await agentRef.current?.getWorkflowRuns();
505
- if (runs)
506
- setWorkflowRuns([...runs]);
507
- },
508
- onTasksChange: (newTasks) => {
509
- setTasks((prev) => {
510
- if (prev.length === newTasks.length &&
511
- prev.every((t, i) => t.id === newTasks[i].id && t.status === newTasks[i].status)) {
512
- return prev;
513
- }
514
- return [...newTasks];
515
- });
516
- },
517
- onPermissionModeChange: (mode) => {
518
- setPermissionModeState(mode);
519
- },
520
- onModelChange: (model) => {
521
- setCurrentModelState(model);
522
- },
523
- onConfiguredModelsChange: (models) => {
524
- setConfiguredModels(models);
525
- },
526
- onLoadingChange: (loading) => {
527
- setIsLoading(loading);
528
- },
529
- onCommandRunningChange: (running) => {
530
- setIsCommandRunning(running);
531
- },
532
- onQueuedMessagesChange: (messages) => {
533
- setQueuedMessages([...messages]);
534
- },
535
- };
536
- try {
537
- // Create the permission callback inside the try block to access showConfirmation
538
- const permissionCallback = async (context) => {
539
- try {
540
- return await showConfirmation(context.toolName, context.toolInput, context.suggestedPrefix, context.hidePersistentOption, context.planContent, context.permissionMode, context.warning);
541
- }
542
- catch {
543
- // If confirmation was cancelled or failed, deny the operation
544
- return {
545
- behavior: "deny",
546
- message: OPERATION_CANCELLED_BY_USER,
547
- };
548
- }
549
- };
550
- const agent = await Agent.create({
551
- callbacks,
552
- restoreSessionId: effectiveRestoreSessionId,
553
- continueLastSession,
554
- logger,
555
- permissionMode: initialPermissionMode ||
556
- (bypassPermissions ? "bypassPermissions" : undefined),
557
- canUseTool: permissionCallback,
558
- stream: true,
559
- plugins: pluginDirs?.map((path) => ({ type: "local", path })),
560
- additionalDirectories,
561
- tools,
562
- allowedTools,
563
- disallowedTools,
564
- workdir,
565
- worktreeName: worktreeSession?.name,
566
- isNewWorktree: worktreeSession?.isNew,
567
- model,
568
- mcpServers,
569
- });
570
- agentRef.current = agent;
571
- // Inject worktree session into this agent's own DI container so system
572
- // prompts and permission checks reflect the CLI -w worktree. This state is
573
- // per-session, not process-global (see docs/specs/multi-agent/worktree.md FR-042).
574
- if (worktreeSession) {
575
- const session = {
576
- originalCwd: originalCwd ?? worktreeSession.repoRoot,
577
- worktreePath: worktreeSession.path,
578
- worktreeBranch: worktreeSession.branch,
579
- worktreeName: worktreeSession.name,
580
- isNew: worktreeSession.isNew,
581
- repoRoot: worktreeSession.repoRoot,
582
- };
583
- agent.setWorktreeSession(session);
584
- }
585
- // Get initial state — snapshot the SDK messages (never hold live
586
- // references; see snapshotMessage)
587
- setSessionId(agent.sessionId);
588
- setMessages(agent.messages.map(snapshotMessage));
589
- setIsLoading(agent.isLoading);
590
- setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
591
- setIsCommandRunning(agent.isCommandRunning);
592
- setIsCompacting(agent.isCompacting);
593
- setPermissionModeState(agent.getPermissionMode());
594
- setWorkingDirectory(agent.workingDirectory);
595
- setCurrentModelState(agent.getModelConfig().model || "");
596
- setConfiguredModels(agent.getConfiguredModels());
597
- setMaxInputTokens(agent.getMaxInputTokens());
598
- // Get initial MCP servers state
599
- const initialMcpServers = agent.getMcpServers?.() || [];
600
- setMcpServerStatuses(initialMcpServers);
601
- // Get initial commands
602
- const agentSlashCommands = agent.getSlashCommands?.() || [];
603
- setSlashCommands(agentSlashCommands);
604
- // Get initial agent definitions
605
- const initialAgentDefinitions = agent.getSubagentConfigurations?.() || [];
606
- setAgentDefinitions(initialAgentDefinitions);
607
- }
608
- catch (error) {
609
- console.error("Failed to initialize AI manager:", error);
610
- }
611
- }, [
612
- restoreSessionId,
613
- continueLastSession,
614
- bypassPermissions,
615
- showConfirmation,
616
- pluginDirs,
617
- additionalDirectories,
618
- tools,
619
- allowedTools,
620
- disallowedTools,
621
- workdir,
622
- worktreeSession,
623
- originalCwd,
624
- model,
625
- initialPermissionMode,
626
- throttledContentUpdate,
627
- throttledReasoningUpdate,
628
- throttledToolBlockUpdate,
629
- mcpServers,
630
- ]);
631
- // Recreate agent (e.g. after plugin install) — destroys current agent and reinitializes
632
- const recreateAgent = useCallback(() => {
633
- const currentSessionId = agentRef.current?.sessionId;
634
- if (agentRef.current) {
635
- try {
636
- agentRef.current.destroy();
637
- }
638
- catch {
639
- // Ignore destroy errors
640
- }
641
- }
642
- agentRef.current = null;
643
- setMessages([]);
644
- setMcpServerStatuses([]);
645
- setSlashCommands([]);
646
- setAgentDefinitions([]);
647
- setSessionId("");
648
- setIsLoading(false);
649
- setLatestTotalTokens(0);
650
- setMaxInputTokens(200000);
651
- setIsCommandRunning(false);
652
- setIsCompacting(false);
653
- if (currentSessionId) {
654
- initializeAgent(currentSessionId);
655
- }
656
- }, [initializeAgent]);
657
- // Run initial agent creation
658
- useEffect(() => {
659
- initializeAgent();
660
- }, [initializeAgent]);
661
- // Cleanup on unmount
662
- useEffect(() => {
663
- return () => {
664
- throttledContentUpdate.cancel();
665
- throttledReasoningUpdate.cancel();
666
- throttledToolBlockUpdate.cancel();
667
- if (agentRef.current) {
668
- try {
669
- // Display usage summary before cleanup
670
- const usages = agentRef.current.usages;
671
- const sessionFilePath = agentRef.current.sessionFilePath;
672
- displayUsageSummary(usages, sessionFilePath);
673
- }
674
- catch {
675
- // Silently ignore usage summary errors during cleanup
676
- }
677
- agentRef.current.destroy();
678
- }
679
- };
680
- }, [
681
- throttledContentUpdate,
682
- throttledReasoningUpdate,
683
- throttledToolBlockUpdate,
684
- ]);
685
- // Trigger WorktreeRemove hook BEFORE agent destruction
686
- const triggerWorktreeRemoveHook = useCallback(async (worktreePath) => {
687
- await agentRef.current?.triggerWorktreeRemoveHook(worktreePath);
688
- }, []);
689
- // Send message function (including judgment logic)
690
- const sendMessage = useCallback(async (content, images, longTextMap) => {
691
- // Check if there's content to send (text content or image attachments)
692
- const hasTextContent = content.trim();
693
- const hasImageAttachments = images && images.length > 0;
694
- if (!hasTextContent && !hasImageAttachments)
695
- return;
696
- try {
697
- const expandedContent = longTextMap
698
- ? expandLongTextPlaceholders(content, longTextMap)
699
- : content;
700
- // Handle bash mode - check if it's a bash command (starts with ! and only one line)
701
- if (expandedContent.startsWith("!") &&
702
- !expandedContent.includes("\n") &&
703
- !hasImageAttachments) {
704
- const command = expandedContent.substring(1).trim();
705
- if (command) {
706
- await agentRef.current?.bang(command);
707
- return;
708
- }
709
- }
710
- try {
711
- await agentRef.current?.sendMessage(expandedContent, images);
712
- }
713
- catch (error) {
714
- console.error("Failed to send message:", error);
715
- }
716
- }
717
- catch (error) {
718
- console.error("Failed to send message:", error);
719
- }
720
- }, []);
721
- const askBtw = useCallback(async (question, abortSignal, onContent) => {
722
- if (!agentRef.current) {
723
- throw new Error("Agent not initialized");
724
- }
725
- return await agentRef.current.askBtw(question, abortSignal, onContent);
726
- }, []);
727
- const clearMessages = useCallback(async () => {
728
- await agentRef.current?.clearMessages();
729
- refreshMessages();
730
- forceRemount();
731
- }, [refreshMessages, forceRemount]);
732
- const compact = useCallback(async (instructions) => {
733
- await agentRef.current?.compact(instructions);
734
- refreshMessages();
735
- forceRemount();
736
- }, [refreshMessages, forceRemount]);
737
- // /add-dir: add a directory to the session Safe Zone (session-level, with
738
- // optional --remember persistence). Bare command shows usage + current list.
739
- const addDir = useCallback(async (args) => {
740
- const agent = agentRef.current;
741
- if (!agent)
742
- return;
743
- // Show the result as a UI-only assistant text message (same display path
744
- // as error blocks; not persisted to session history).
745
- const showResult = (content) => {
746
- setMessages((prev) => [
747
- ...prev,
748
- {
749
- id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
750
- role: "assistant",
751
- timestamp: new Date().toISOString(),
752
- blocks: [{ type: "text", content, stage: "end" }],
753
- },
754
- ]);
755
- };
756
- const rawArgs = (args ?? "").trim();
757
- if (!rawArgs) {
758
- const dirs = agent.getAdditionalDirectories();
759
- const list = dirs.length > 0
760
- ? dirs.map((dir) => ` - ${dir}`).join("\n")
761
- : " (none)";
762
- showResult(`Usage: /add-dir <path> [--remember]\n\nAdditional working directories:\n${list}`);
763
- return;
764
- }
765
- const remember = rawArgs.includes("--remember");
766
- const dir = rawArgs.replace("--remember", "").trim();
767
- if (!dir) {
768
- showResult("Usage: /add-dir <path> [--remember]");
769
- return;
770
- }
771
- await agent.addAdditionalDirectory(dir, { remember });
772
- showResult(`Added ${dir} to the session Safe Zone${remember ? " (remembered)" : ""}.`);
773
- }, []);
774
- // Unified interrupt method, interrupt both AI messages and command execution
775
- const abortMessage = useCallback(() => {
776
- agentRef.current?.abortMessage();
777
- }, []);
778
- const recallQueuedMessage = useCallback(() => {
779
- return agentRef.current?.recallQueuedMessage() ?? null;
780
- }, []);
781
- const removeQueuedMessageById = useCallback((id) => {
782
- return agentRef.current?.removeQueuedMessageById(id) ?? false;
783
- }, []);
784
- // Permission management methods
785
- const setPermissionMode = useCallback((mode) => {
786
- setPermissionModeState((prev) => {
787
- if (prev === mode)
788
- return prev;
789
- if (agentRef.current && agentRef.current.getPermissionMode() !== mode) {
790
- agentRef.current.setPermissionMode(mode);
791
- }
792
- return mode;
793
- });
794
- }, []);
795
- // MCP management methods - delegate to Agent
796
- const connectMcpServer = useCallback(async (serverName) => {
797
- return (await agentRef.current?.connectMcpServer(serverName)) ?? false;
798
- }, []);
799
- const disconnectMcpServer = useCallback(async (serverName) => {
800
- return (await agentRef.current?.disconnectMcpServer(serverName)) ?? false;
801
- }, []);
802
- // Background task management methods - delegate to Agent
803
- const getBackgroundTaskOutput = useCallback((taskId) => {
804
- if (!agentRef.current)
805
- return null;
806
- return agentRef.current.getBackgroundTaskOutput(taskId);
807
- }, []);
808
- const stopBackgroundTask = useCallback((taskId) => {
809
- if (!agentRef.current)
810
- return false;
811
- return agentRef.current.stopBackgroundTask(taskId);
812
- }, []);
813
- const stopWorkflowRun = useCallback((runId) => {
814
- agentRef.current?.stopWorkflowRun(runId);
815
- }, []);
816
- const hasSlashCommand = useCallback((commandId) => {
817
- if (!agentRef.current)
818
- return false;
819
- return agentRef.current.hasSlashCommand(commandId);
820
- }, []);
821
- // Queue processing helper
822
- const processNextConfirmation = useCallback(() => {
823
- if (confirmationQueue.length > 0 && !isConfirmationVisible) {
824
- const next = confirmationQueue[0];
825
- setCurrentConfirmation(next);
826
- setConfirmingTool({
827
- name: next.toolName,
828
- input: next.toolInput,
829
- suggestedPrefix: next.suggestedPrefix,
830
- hidePersistentOption: next.hidePersistentOption,
831
- planContent: next.planContent,
832
- permissionMode: next.permissionMode,
833
- warning: next.warning,
834
- });
835
- setIsConfirmationVisible(true);
836
- setConfirmationQueue((prev) => prev.slice(1));
837
- }
838
- }, [confirmationQueue, isConfirmationVisible]);
839
- // Process queue when queue changes or confirmation is hidden
840
- useEffect(() => {
841
- processNextConfirmation();
842
- }, [processNextConfirmation]);
843
- const hideConfirmation = useCallback(() => {
844
- setIsConfirmationVisible(false);
845
- setConfirmingTool(undefined);
846
- setCurrentConfirmation(null);
847
- }, []);
848
- const handleConfirmationDecision = useCallback((decision) => {
849
- if (currentConfirmation) {
850
- currentConfirmation.resolver(decision);
851
- }
852
- hideConfirmation();
853
- }, [currentConfirmation, hideConfirmation]);
854
- const handleConfirmationCancel = useCallback(() => {
855
- if (currentConfirmation) {
856
- currentConfirmation.reject();
857
- }
858
- agentRef.current?.abortMessage();
859
- hideConfirmation();
860
- }, [currentConfirmation, hideConfirmation]);
861
- const backgroundCurrentTask = useCallback(() => {
862
- agentRef.current?.backgroundCurrentTask();
863
- }, []);
864
- const handleRewindSelect = useCallback(async (index) => {
865
- if (agentRef.current) {
866
- try {
867
- await agentRef.current.truncateHistory(index);
868
- refreshMessages();
869
- forceRemount();
870
- }
871
- catch (error) {
872
- logger.error("Failed to rewind:", error);
873
- }
874
- }
875
- }, [forceRemount, refreshMessages]);
876
- const getFullMessageThread = useCallback(async () => {
877
- if (agentRef.current) {
878
- return await agentRef.current.getFullMessageThread();
879
- }
880
- return { messages: [], sessionIds: [] };
881
- }, []);
882
- const getGatewayConfig = useCallback(() => {
883
- if (!agentRef.current) {
884
- return { baseURL: "" };
885
- }
886
- return agentRef.current.getGatewayConfig();
887
- }, []);
888
- const getModelConfig = useCallback(() => {
889
- if (!agentRef.current) {
890
- return { model: "", fastModel: "" };
891
- }
892
- return agentRef.current.getModelConfig();
893
- }, []);
894
- const getConfiguredModels = useCallback(() => {
895
- if (!agentRef.current) {
896
- return [];
897
- }
898
- return agentRef.current.getConfiguredModels();
899
- }, []);
900
- const setModel = useCallback((model) => {
901
- if (agentRef.current) {
902
- agentRef.current.setModel(model);
903
- setCurrentModelState(model);
904
- }
905
- }, []);
906
- // Listen for Ctrl+O hotkey to toggle collapse/expand state and ESC to cancel confirmation
907
- useInput((input, key) => {
908
- if (key.ctrl && input === "o") {
909
- // Use ref to get the current value to avoid stale closure
910
- const nextExpanded = !isExpandedRef.current;
911
- setIsExpanded(nextExpanded);
912
- isExpandedRef.current = nextExpanded;
913
- if (nextExpanded) {
914
- // Transitioning to EXPANDED: Freeze the current view
915
- // Incremental updates are skipped while expanded (isExpandedRef guard)
916
- }
917
- else {
918
- // Transitioning to COLLAPSED: Restore from agent's actual state
919
- refreshMessages();
920
- }
921
- // Force remount to ensure Static items re-render
922
- forceRemount();
923
- }
924
- if (key.ctrl && input === "t") {
925
- setIsTaskListVisible((prev) => !prev);
926
- }
927
- // Handle ESC key to cancel confirmation
928
- if (key.escape && isConfirmationVisible) {
929
- handleConfirmationCancel();
930
- }
931
- });
932
- const contextValue = {
933
- messages,
934
- isLoading,
935
- isCommandRunning,
936
- isExpanded,
937
- isTaskListVisible,
938
- setIsTaskListVisible,
939
- isBtwActive,
940
- setIsBtwActive,
941
- queuedMessages,
942
- sessionId,
943
- sendMessage,
944
- askBtw,
945
- clearMessages,
946
- compact,
947
- addDir,
948
- abortMessage,
949
- recallQueuedMessage,
950
- removeQueuedMessageById,
951
- latestTotalTokens,
952
- maxInputTokens,
953
- currentModel,
954
- configuredModels,
955
- getConfiguredModels,
956
- setModel,
957
- isCompacting,
958
- mcpServers: mcpServerStatuses,
959
- connectMcpServer,
960
- disconnectMcpServer,
961
- backgroundTasks,
962
- workflowRuns,
963
- stopWorkflowRun,
964
- tasks,
965
- getBackgroundTaskOutput,
966
- stopBackgroundTask,
967
- slashCommands,
968
- hasSlashCommand,
969
- agentDefinitions,
970
- permissionMode,
971
- setPermissionMode,
972
- isConfirmationVisible,
973
- hasPendingConfirmations: confirmationQueue.length > 0,
974
- confirmingTool,
975
- showConfirmation,
976
- hideConfirmation,
977
- handleConfirmationDecision,
978
- handleConfirmationCancel,
979
- backgroundCurrentTask,
980
- remountKey,
981
- forceRemount,
982
- handleRewindSelect,
983
- getFullMessageThread,
984
- getGatewayConfig,
985
- getModelConfig,
986
- workingDirectory,
987
- version,
988
- workdir,
989
- recreateAgent,
990
- triggerWorktreeRemoveHook,
991
- };
992
- return (_jsx(ChatContext.Provider, { value: contextValue, children: children }));
993
- };