wave-code 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  2. package/dist/components/BackgroundTaskManager.js +36 -25
  3. package/dist/components/ChatInterface.js +2 -2
  4. package/dist/components/CompactDisplay.d.ts +9 -0
  5. package/dist/components/CompactDisplay.d.ts.map +1 -0
  6. package/dist/components/{CompressDisplay.js → CompactDisplay.js} +2 -2
  7. package/dist/components/InputBox.d.ts.map +1 -1
  8. package/dist/components/InputBox.js +2 -2
  9. package/dist/components/LoadingIndicator.d.ts +2 -2
  10. package/dist/components/LoadingIndicator.d.ts.map +1 -1
  11. package/dist/components/LoadingIndicator.js +2 -2
  12. package/dist/components/MarketplaceAddForm.d.ts.map +1 -1
  13. package/dist/components/MarketplaceAddForm.js +48 -17
  14. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  15. package/dist/components/MarketplaceDetail.js +2 -1
  16. package/dist/components/MarketplaceList.d.ts.map +1 -1
  17. package/dist/components/MarketplaceList.js +2 -1
  18. package/dist/components/McpManager.d.ts.map +1 -1
  19. package/dist/components/McpManager.js +22 -27
  20. package/dist/components/MessageBlockItem.js +2 -2
  21. package/dist/components/PluginDetail.d.ts.map +1 -1
  22. package/dist/components/PluginDetail.js +22 -22
  23. package/dist/components/PluginManagerShell.d.ts +1 -0
  24. package/dist/components/PluginManagerShell.d.ts.map +1 -1
  25. package/dist/components/PluginManagerShell.js +2 -2
  26. package/dist/components/PluginManagerTypes.d.ts +2 -2
  27. package/dist/components/PluginManagerTypes.d.ts.map +1 -1
  28. package/dist/contexts/useChat.d.ts +2 -1
  29. package/dist/contexts/useChat.d.ts.map +1 -1
  30. package/dist/contexts/useChat.js +127 -100
  31. package/dist/hooks/usePluginManager.d.ts +3 -1
  32. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  33. package/dist/hooks/usePluginManager.js +16 -7
  34. package/dist/reducers/backgroundTaskManagerReducer.d.ts +43 -0
  35. package/dist/reducers/backgroundTaskManagerReducer.d.ts.map +1 -0
  36. package/dist/reducers/backgroundTaskManagerReducer.js +23 -0
  37. package/dist/reducers/marketplaceAddFormReducer.d.ts +24 -0
  38. package/dist/reducers/marketplaceAddFormReducer.d.ts.map +1 -0
  39. package/dist/reducers/marketplaceAddFormReducer.js +18 -0
  40. package/dist/reducers/mcpManagerReducer.d.ts +16 -0
  41. package/dist/reducers/mcpManagerReducer.d.ts.map +1 -0
  42. package/dist/reducers/mcpManagerReducer.js +15 -0
  43. package/dist/reducers/pluginDetailReducer.d.ts +25 -0
  44. package/dist/reducers/pluginDetailReducer.d.ts.map +1 -0
  45. package/dist/reducers/pluginDetailReducer.js +38 -0
  46. package/dist/utils/usageSummary.d.ts +1 -1
  47. package/dist/utils/usageSummary.d.ts.map +1 -1
  48. package/dist/utils/usageSummary.js +7 -7
  49. package/package.json +2 -2
  50. package/src/components/BackgroundTaskManager.tsx +32 -34
  51. package/src/components/ChatInterface.tsx +3 -3
  52. package/src/components/{CompressDisplay.tsx → CompactDisplay.tsx} +5 -5
  53. package/src/components/InputBox.tsx +7 -1
  54. package/src/components/LoadingIndicator.tsx +5 -5
  55. package/src/components/MarketplaceAddForm.tsx +76 -22
  56. package/src/components/MarketplaceDetail.tsx +4 -0
  57. package/src/components/MarketplaceList.tsx +4 -0
  58. package/src/components/McpManager.tsx +30 -34
  59. package/src/components/MessageBlockItem.tsx +3 -3
  60. package/src/components/PluginDetail.tsx +25 -33
  61. package/src/components/PluginManagerShell.tsx +3 -2
  62. package/src/components/PluginManagerTypes.ts +8 -2
  63. package/src/contexts/useChat.tsx +60 -26
  64. package/src/hooks/usePluginManager.ts +18 -7
  65. package/src/reducers/backgroundTaskManagerReducer.ts +60 -0
  66. package/src/reducers/marketplaceAddFormReducer.ts +35 -0
  67. package/src/reducers/mcpManagerReducer.ts +31 -0
  68. package/src/reducers/pluginDetailReducer.ts +58 -0
  69. package/src/utils/usageSummary.ts +8 -8
  70. package/dist/components/CompressDisplay.d.ts +0 -9
  71. package/dist/components/CompressDisplay.d.ts.map +0 -1
@@ -1,6 +1,10 @@
1
- import React, { useState, useRef, useEffect } from "react";
1
+ import React, { useReducer } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import { McpServerStatus } from "wave-agent-sdk";
4
+ import {
5
+ mcpManagerReducer,
6
+ type McpManagerState,
7
+ } from "../reducers/mcpManagerReducer.js";
4
8
 
5
9
  export interface McpManagerProps {
6
10
  onCancel: () => void;
@@ -9,33 +13,25 @@ export interface McpManagerProps {
9
13
  onDisconnectServer: (serverName: string) => Promise<boolean>;
10
14
  }
11
15
 
16
+ const initialState: McpManagerState = {
17
+ selectedIndex: 0,
18
+ viewMode: "list",
19
+ };
20
+
12
21
  export const McpManager: React.FC<McpManagerProps> = ({
13
22
  onCancel,
14
23
  servers,
15
24
  onConnectServer,
16
25
  onDisconnectServer,
17
26
  }) => {
18
- const [selectedIndex, setSelectedIndex] = useState(0);
19
- const [viewMode, setViewMode] = useState<"list" | "detail">("list");
20
-
21
- // Keep ref in sync with state to avoid stale closures in useInput
22
- const selectedIndexRef = useRef(selectedIndex);
23
- const viewModeRef = useRef(viewMode);
24
-
25
- useEffect(() => {
26
- selectedIndexRef.current = selectedIndex;
27
- }, [selectedIndex]);
28
-
29
- useEffect(() => {
30
- viewModeRef.current = viewMode;
31
- }, [viewMode]);
27
+ const [state, dispatch] = useReducer(mcpManagerReducer, initialState);
32
28
 
33
29
  // Dynamically calculate selectedServer based on selectedIndex and servers
34
30
  const selectedServer =
35
- viewMode === "detail" &&
31
+ state.viewMode === "detail" &&
36
32
  servers.length > 0 &&
37
- selectedIndex < servers.length
38
- ? servers[selectedIndex]
33
+ state.selectedIndex < servers.length
34
+ ? servers[state.selectedIndex]
39
35
  : null;
40
36
 
41
37
  const formatTime = (timestamp: number): string => {
@@ -78,15 +74,15 @@ export const McpManager: React.FC<McpManagerProps> = ({
78
74
 
79
75
  useInput((input, key) => {
80
76
  if (key.return) {
81
- if (viewModeRef.current === "list") {
82
- setViewMode("detail");
77
+ if (state.viewMode === "list") {
78
+ dispatch({ type: "SET_VIEW_MODE", viewMode: "detail" });
83
79
  }
84
80
  return;
85
81
  }
86
82
 
87
83
  if (key.escape) {
88
- if (viewModeRef.current === "detail") {
89
- setViewMode("list");
84
+ if (state.viewMode === "detail") {
85
+ dispatch({ type: "SET_VIEW_MODE", viewMode: "list" });
90
86
  } else {
91
87
  onCancel();
92
88
  }
@@ -94,18 +90,18 @@ export const McpManager: React.FC<McpManagerProps> = ({
94
90
  }
95
91
 
96
92
  if (key.upArrow) {
97
- setSelectedIndex((prev) => Math.max(0, prev - 1));
93
+ dispatch({ type: "MOVE_UP", serverCount: servers.length });
98
94
  return;
99
95
  }
100
96
 
101
97
  if (key.downArrow) {
102
- setSelectedIndex((prev) => Math.min(servers.length - 1, prev + 1));
98
+ dispatch({ type: "MOVE_DOWN", serverCount: servers.length });
103
99
  return;
104
100
  }
105
101
 
106
102
  // Hotkeys for server actions
107
103
  if (input === "c") {
108
- const server = servers[selectedIndexRef.current];
104
+ const server = servers[state.selectedIndex];
109
105
  if (
110
106
  server &&
111
107
  (server.status === "disconnected" || server.status === "error")
@@ -116,7 +112,7 @@ export const McpManager: React.FC<McpManagerProps> = ({
116
112
  }
117
113
 
118
114
  if (input === "d") {
119
- const server = servers[selectedIndexRef.current];
115
+ const server = servers[state.selectedIndex];
120
116
  if (server && server.status === "connected") {
121
117
  handleDisconnect(server.name);
122
118
  }
@@ -124,7 +120,7 @@ export const McpManager: React.FC<McpManagerProps> = ({
124
120
  }
125
121
  });
126
122
 
127
- if (viewMode === "detail" && selectedServer) {
123
+ if (state.viewMode === "detail" && selectedServer) {
128
124
  return (
129
125
  <Box
130
126
  flexDirection="column"
@@ -273,10 +269,10 @@ export const McpManager: React.FC<McpManagerProps> = ({
273
269
  {servers.map((server, index) => (
274
270
  <Box key={server.name} flexDirection="column">
275
271
  <Text
276
- color={index === selectedIndex ? "black" : "white"}
277
- backgroundColor={index === selectedIndex ? "cyan" : undefined}
272
+ color={index === state.selectedIndex ? "black" : "white"}
273
+ backgroundColor={index === state.selectedIndex ? "cyan" : undefined}
278
274
  >
279
- {index === selectedIndex ? "▶ " : " "}
275
+ {index === state.selectedIndex ? "▶ " : " "}
280
276
  {index + 1}.{" "}
281
277
  <Text color={getStatusColor(server.status)}>
282
278
  {getStatusIcon(server.status)}
@@ -286,7 +282,7 @@ export const McpManager: React.FC<McpManagerProps> = ({
286
282
  <Text color="green"> · {server.toolCount} tools</Text>
287
283
  )}
288
284
  </Text>
289
- {index === selectedIndex && (
285
+ {index === state.selectedIndex && (
290
286
  <Box marginLeft={4} flexDirection="column">
291
287
  <Text color="gray" dimColor>
292
288
  {server.config.command}
@@ -305,11 +301,11 @@ export const McpManager: React.FC<McpManagerProps> = ({
305
301
  <Box marginTop={1}>
306
302
  <Text dimColor>
307
303
  ↑/↓ to select · Enter to view ·{" "}
308
- {servers[selectedIndex]?.status === "disconnected" ||
309
- servers[selectedIndex]?.status === "error"
304
+ {servers[state.selectedIndex]?.status === "disconnected" ||
305
+ servers[state.selectedIndex]?.status === "error"
310
306
  ? "c to connect · "
311
307
  : ""}
312
- {servers[selectedIndex]?.status === "connected"
308
+ {servers[state.selectedIndex]?.status === "connected"
313
309
  ? "d to disconnect · "
314
310
  : ""}
315
311
  Esc to close
@@ -4,7 +4,7 @@ import type { Message, MessageBlock } from "wave-agent-sdk";
4
4
  import { MessageSource } from "wave-agent-sdk";
5
5
  import { BangDisplay } from "./BangDisplay.js";
6
6
  import { ToolDisplay } from "./ToolDisplay.js";
7
- import { CompressDisplay } from "./CompressDisplay.js";
7
+ import { CompactDisplay } from "./CompactDisplay.js";
8
8
  import { ReasoningDisplay } from "./ReasoningDisplay.js";
9
9
  import { Markdown } from "./Markdown.js";
10
10
  import { TaskNotificationMessage } from "./TaskNotificationMessage.js";
@@ -79,8 +79,8 @@ export const MessageBlockItem = ({
79
79
  </Box>
80
80
  )}
81
81
 
82
- {block.type === "compress" && (
83
- <CompressDisplay block={block} isExpanded={isExpanded} />
82
+ {block.type === "compact" && (
83
+ <CompactDisplay block={block} isExpanded={isExpanded} />
84
84
  )}
85
85
 
86
86
  {block.type === "reasoning" && (
@@ -1,6 +1,10 @@
1
- import React, { useState, useRef, useEffect } from "react";
1
+ import React, { useReducer } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
3
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
4
+ import {
5
+ pluginDetailReducer,
6
+ type PluginDetailState,
7
+ } from "../reducers/pluginDetailReducer.js";
4
8
 
5
9
  const SCOPES = [
6
10
  { id: "project", label: "Install for all collaborators (project scope)" },
@@ -11,20 +15,10 @@ const SCOPES = [
11
15
  export const PluginDetail: React.FC = () => {
12
16
  const { state, discoverablePlugins, installedPlugins, actions } =
13
17
  usePluginManagerContext();
14
- const [selectedScopeIndex, setSelectedScopeIndex] = useState(0);
15
- const [selectedActionIndex, setSelectedActionIndex] = useState(0);
16
-
17
- // Keep refs in sync with state to avoid stale closures in useInput
18
- const selectedScopeIndexRef = useRef(selectedScopeIndex);
19
- const selectedActionIndexRef = useRef(selectedActionIndex);
20
-
21
- useEffect(() => {
22
- selectedScopeIndexRef.current = selectedScopeIndex;
23
- }, [selectedScopeIndex]);
24
-
25
- useEffect(() => {
26
- selectedActionIndexRef.current = selectedActionIndex;
27
- }, [selectedActionIndex]);
18
+ const [detailState, dispatch] = useReducer(pluginDetailReducer, {
19
+ selectedScopeIndex: 0,
20
+ selectedActionIndex: 0,
21
+ } as PluginDetailState);
28
22
 
29
23
  const plugin =
30
24
  discoverablePlugins.find(
@@ -48,22 +42,20 @@ export const PluginDetail: React.FC = () => {
48
42
  );
49
43
  actions.setView(isFromDiscover ? "DISCOVER" : "INSTALLED");
50
44
  } else if (key.upArrow) {
51
- setSelectedActionIndex((prev) =>
52
- prev > 0 ? prev - 1 : INSTALLED_ACTIONS.length - 1,
53
- );
54
- setSelectedScopeIndex((prev) =>
55
- prev > 0 ? prev - 1 : SCOPES.length - 1,
56
- );
45
+ dispatch({
46
+ type: "MOVE_ACTION_UP",
47
+ maxIndex: INSTALLED_ACTIONS.length - 1,
48
+ });
49
+ dispatch({ type: "MOVE_SCOPE_UP", maxIndex: SCOPES.length - 1 });
57
50
  } else if (key.downArrow) {
58
- setSelectedActionIndex((prev) =>
59
- prev < INSTALLED_ACTIONS.length - 1 ? prev + 1 : 0,
60
- );
61
- setSelectedScopeIndex((prev) =>
62
- prev < SCOPES.length - 1 ? prev + 1 : 0,
63
- );
51
+ dispatch({
52
+ type: "MOVE_ACTION_DOWN",
53
+ maxIndex: INSTALLED_ACTIONS.length - 1,
54
+ });
55
+ dispatch({ type: "MOVE_SCOPE_DOWN", maxIndex: SCOPES.length - 1 });
64
56
  } else if (key.return && plugin && !state.isLoading) {
65
57
  if (isInstalledAndEnabled) {
66
- const action = INSTALLED_ACTIONS[selectedActionIndexRef.current].id;
58
+ const action = INSTALLED_ACTIONS[detailState.selectedActionIndex].id;
67
59
  if (action === "uninstall") {
68
60
  actions.uninstallPlugin(plugin.name, plugin.marketplace);
69
61
  } else {
@@ -73,7 +65,7 @@ export const PluginDetail: React.FC = () => {
73
65
  actions.installPlugin(
74
66
  plugin.name,
75
67
  plugin.marketplace,
76
- SCOPES[selectedScopeIndexRef.current].id,
68
+ SCOPES[detailState.selectedScopeIndex].id,
77
69
  );
78
70
  }
79
71
  }
@@ -121,14 +113,14 @@ export const PluginDetail: React.FC = () => {
121
113
  <Text
122
114
  key={action.id}
123
115
  color={
124
- index === selectedActionIndex
116
+ index === detailState.selectedActionIndex
125
117
  ? state.isLoading
126
118
  ? "gray"
127
119
  : "yellow"
128
120
  : undefined
129
121
  }
130
122
  >
131
- {index === selectedActionIndex ? "> " : " "}
123
+ {index === detailState.selectedActionIndex ? "> " : " "}
132
124
  {action.label}
133
125
  </Text>
134
126
  ))}
@@ -147,14 +139,14 @@ export const PluginDetail: React.FC = () => {
147
139
  <Text
148
140
  key={scope.id}
149
141
  color={
150
- index === selectedScopeIndex
142
+ index === detailState.selectedScopeIndex
151
143
  ? state.isLoading
152
144
  ? "gray"
153
145
  : "green"
154
146
  : undefined
155
147
  }
156
148
  >
157
- {index === selectedScopeIndex ? "> " : " "}
149
+ {index === detailState.selectedScopeIndex ? "> " : " "}
158
150
  {scope.label}
159
151
  </Text>
160
152
  ))}
@@ -13,8 +13,9 @@ import { PluginManagerContext } from "../contexts/PluginManagerContext.js";
13
13
  export const PluginManagerShell: React.FC<{
14
14
  children?: React.ReactNode;
15
15
  onCancel?: () => void;
16
- }> = ({ children, onCancel }) => {
17
- const pluginManager = usePluginManager();
16
+ onPluginInstalled?: () => void;
17
+ }> = ({ children, onCancel, onPluginInstalled }) => {
18
+ const pluginManager = usePluginManager({ onPluginInstalled });
18
19
  const { state, actions, discoverablePlugins } = pluginManager;
19
20
 
20
21
  const setView = (view: ViewType) => {
@@ -33,8 +33,14 @@ export interface PluginManagerContextType {
33
33
  actions: {
34
34
  setView: (view: ViewType) => void;
35
35
  setSelectedId: (id: string | null) => void;
36
- addMarketplace: (source: string) => Promise<void>;
37
- removeMarketplace: (name: string) => Promise<void>;
36
+ addMarketplace: (
37
+ source: string,
38
+ scope?: "user" | "project" | "local",
39
+ ) => Promise<void>;
40
+ removeMarketplace: (
41
+ name: string,
42
+ scope?: "user" | "project" | "local",
43
+ ) => Promise<void>;
38
44
  updateMarketplace: (name: string) => Promise<void>;
39
45
  installPlugin: (
40
46
  name: string,
@@ -37,7 +37,7 @@ export interface ChatContextType {
37
37
  messages: Message[];
38
38
  isLoading: boolean;
39
39
  isCommandRunning: boolean;
40
- isCompressing: boolean;
40
+ isCompacting: boolean;
41
41
  // Message display state
42
42
  isExpanded: boolean;
43
43
  isTaskListVisible: boolean;
@@ -117,6 +117,8 @@ export interface ChatContextType {
117
117
  workingDirectory: string;
118
118
  version?: string;
119
119
  workdir?: string;
120
+ // Agent recreation (e.g. after plugin install)
121
+ recreateAgent: () => void;
120
122
  }
121
123
 
122
124
  const ChatContext = createContext<ChatContextType | null>(null);
@@ -202,7 +204,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
202
204
  const [latestTotalTokens, setlatestTotalTokens] = useState(0);
203
205
  const [sessionId, setSessionId] = useState("");
204
206
  const [isCommandRunning, setIsCommandRunning] = useState(false);
205
- const [isCompressing, setIsCompressing] = useState(false);
207
+ const [isCompacting, setIsCompacting] = useState(false);
206
208
  const [currentModel, setCurrentModelState] = useState("");
207
209
  const [configuredModels, setConfiguredModels] = useState<string[]>([]);
208
210
  const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]);
@@ -332,8 +334,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
332
334
  );
333
335
 
334
336
  // Initialize AI manager
335
- useEffect(() => {
336
- const initializeAgent = async () => {
337
+ const initializeAgent = useCallback(
338
+ async (restoreSessionIdOverride?: string) => {
339
+ const effectiveRestoreSessionId =
340
+ restoreSessionIdOverride ?? restoreSessionId;
341
+
337
342
  const callbacks: AgentCallbacks = {
338
343
  onMessagesChange: () => {
339
344
  throttledSetMessages();
@@ -347,8 +352,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
347
352
  onLatestTotalTokensChange: (tokens) => {
348
353
  throttledSetTokens(tokens);
349
354
  },
350
- onCompressionStateChange: (isCompressingState) => {
351
- setIsCompressing(isCompressingState);
355
+ onCompactionStateChange: (isCompactingState) => {
356
+ setIsCompacting(isCompactingState);
352
357
  },
353
358
  onBackgroundTasksChange: (tasks) => {
354
359
  setBackgroundTasks([...tasks]);
@@ -400,7 +405,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
400
405
 
401
406
  const agent = await Agent.create({
402
407
  callbacks,
403
- restoreSessionId,
408
+ restoreSessionId: effectiveRestoreSessionId,
404
409
  continueLastSession,
405
410
  logger,
406
411
  permissionMode:
@@ -426,7 +431,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
426
431
  setIsLoading(agent.isLoading);
427
432
  setlatestTotalTokens(agent.latestTotalTokens);
428
433
  setIsCommandRunning(agent.isCommandRunning);
429
- setIsCompressing(agent.isCompressing);
434
+ setIsCompacting(agent.isCompacting);
430
435
  setPermissionModeState(agent.getPermissionMode());
431
436
  setWorkingDirectory(agent.workingDirectory);
432
437
  setCurrentModelState(agent.getModelConfig().model);
@@ -442,25 +447,53 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
442
447
  } catch (error) {
443
448
  console.error("Failed to initialize AI manager:", error);
444
449
  }
445
- };
450
+ },
451
+ [
452
+ restoreSessionId,
453
+ continueLastSession,
454
+ bypassPermissions,
455
+ showConfirmation,
456
+ pluginDirs,
457
+ tools,
458
+ allowedTools,
459
+ disallowedTools,
460
+ workdir,
461
+ worktreeSession,
462
+ model,
463
+ initialPermissionMode,
464
+ throttledSetMessages,
465
+ throttledSetTokens,
466
+ ],
467
+ );
446
468
 
469
+ // Recreate agent (e.g. after plugin install) — destroys current agent and reinitializes
470
+ const recreateAgent = useCallback(() => {
471
+ const currentSessionId = agentRef.current?.sessionId;
472
+ if (agentRef.current) {
473
+ try {
474
+ agentRef.current.destroy();
475
+ } catch {
476
+ // Ignore destroy errors
477
+ }
478
+ }
479
+ agentRef.current = null;
480
+ setMessages([]);
481
+ setMcpServers([]);
482
+ setSlashCommands([]);
483
+ setSessionId("");
484
+ setIsLoading(false);
485
+ setlatestTotalTokens(0);
486
+ setIsCommandRunning(false);
487
+ setIsCompacting(false);
488
+ if (currentSessionId) {
489
+ initializeAgent(currentSessionId);
490
+ }
491
+ }, [initializeAgent]);
492
+
493
+ // Run initial agent creation
494
+ useEffect(() => {
447
495
  initializeAgent();
448
- }, [
449
- restoreSessionId,
450
- continueLastSession,
451
- bypassPermissions,
452
- showConfirmation,
453
- pluginDirs,
454
- tools,
455
- allowedTools,
456
- disallowedTools,
457
- workdir,
458
- worktreeSession,
459
- model,
460
- initialPermissionMode,
461
- throttledSetMessages,
462
- throttledSetTokens,
463
- ]);
496
+ }, [initializeAgent]);
464
497
 
465
498
  // Cleanup on unmount
466
499
  useEffect(() => {
@@ -720,7 +753,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
720
753
  configuredModels,
721
754
  getConfiguredModels,
722
755
  setModel,
723
- isCompressing,
756
+ isCompacting,
724
757
  mcpServers,
725
758
  connectMcpServer,
726
759
  disconnectMcpServer,
@@ -751,6 +784,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
751
784
  workingDirectory,
752
785
  version,
753
786
  workdir,
787
+ recreateAgent,
754
788
  };
755
789
 
756
790
  return (
@@ -11,7 +11,9 @@ import {
11
11
  PluginManagerContextType,
12
12
  } from "../components/PluginManagerTypes.js";
13
13
 
14
- export function usePluginManager(): PluginManagerContextType {
14
+ export function usePluginManager(options?: {
15
+ onPluginInstalled?: () => void;
16
+ }): PluginManagerContextType {
15
17
  const [state, setState] = useState<PluginManagerState>({
16
18
  currentView: "DISCOVER",
17
19
  selectedId: null,
@@ -141,16 +143,20 @@ export function usePluginManager(): PluginManagerContextType {
141
143
  }, []);
142
144
 
143
145
  const addMarketplace = useCallback(
144
- async (source: string) => {
146
+ async (source: string, scope: "user" | "project" | "local" = "user") => {
145
147
  clearPluginFeedback();
146
148
  setState((prev: PluginManagerState) => ({
147
149
  ...prev,
148
150
  isLoading: true,
149
151
  }));
150
152
  try {
151
- await pluginCore.addMarketplace(source);
153
+ await pluginCore.addMarketplace(source, scope);
152
154
  await refresh();
153
- setSuccessMessage(`Marketplace added successfully`);
155
+ setSuccessMessage(`Marketplace added successfully (${scope} scope)`);
156
+ setState((prev: PluginManagerState) => ({
157
+ ...prev,
158
+ currentView: "MARKETPLACES",
159
+ }));
154
160
  } catch (error) {
155
161
  setState((prev: PluginManagerState) => ({
156
162
  ...prev,
@@ -163,16 +169,20 @@ export function usePluginManager(): PluginManagerContextType {
163
169
  );
164
170
 
165
171
  const removeMarketplace = useCallback(
166
- async (name: string) => {
172
+ async (name: string, scope?: "user" | "project" | "local") => {
167
173
  clearPluginFeedback();
168
174
  setState((prev: PluginManagerState) => ({
169
175
  ...prev,
170
176
  isLoading: true,
171
177
  }));
172
178
  try {
173
- await pluginCore.removeMarketplace(name);
179
+ await pluginCore.removeMarketplace(name, scope);
174
180
  await refresh();
175
181
  setSuccessMessage(`Marketplace '${name}' removed successfully`);
182
+ setState((prev: PluginManagerState) => ({
183
+ ...prev,
184
+ currentView: "MARKETPLACES",
185
+ }));
176
186
  } catch (error) {
177
187
  setState((prev: PluginManagerState) => ({
178
188
  ...prev,
@@ -222,6 +232,7 @@ export function usePluginManager(): PluginManagerContextType {
222
232
  await pluginCore.installPlugin(pluginId, scope);
223
233
  await refresh();
224
234
  setSuccessMessage(`Plugin '${name}' installed successfully`);
235
+ options?.onPluginInstalled?.();
225
236
  } catch (error) {
226
237
  setState((prev: PluginManagerState) => ({
227
238
  ...prev,
@@ -230,7 +241,7 @@ export function usePluginManager(): PluginManagerContextType {
230
241
  }));
231
242
  }
232
243
  },
233
- [pluginCore, refresh, clearPluginFeedback, setSuccessMessage],
244
+ [pluginCore, refresh, clearPluginFeedback, setSuccessMessage, options],
234
245
  );
235
246
 
236
247
  const uninstallPlugin = useCallback(
@@ -0,0 +1,60 @@
1
+ export interface Task {
2
+ id: string;
3
+ type: string;
4
+ description?: string;
5
+ status: "running" | "completed" | "failed" | "killed";
6
+ startTime: number;
7
+ exitCode?: number;
8
+ runtime?: number;
9
+ outputPath?: string;
10
+ }
11
+
12
+ export interface DetailOutput {
13
+ stdout: string;
14
+ stderr: string;
15
+ status: string;
16
+ outputPath?: string;
17
+ }
18
+
19
+ export interface BackgroundTaskManagerState {
20
+ tasks: Task[];
21
+ selectedIndex: number;
22
+ viewMode: "list" | "detail";
23
+ detailTaskId: string | null;
24
+ detailOutput: DetailOutput | null;
25
+ }
26
+
27
+ export type BackgroundTaskManagerAction =
28
+ | { type: "SET_TASKS"; tasks: Task[] }
29
+ | { type: "SELECT_INDEX"; index: number }
30
+ | { type: "SET_VIEW_MODE"; mode: "list" | "detail" }
31
+ | { type: "SET_DETAIL_TASK_ID"; id: string | null }
32
+ | { type: "SET_DETAIL_OUTPUT"; output: DetailOutput | null }
33
+ | { type: "RESET_DETAIL" };
34
+
35
+ export function backgroundTaskManagerReducer(
36
+ state: BackgroundTaskManagerState,
37
+ action: BackgroundTaskManagerAction,
38
+ ): BackgroundTaskManagerState {
39
+ switch (action.type) {
40
+ case "SET_TASKS":
41
+ return { ...state, tasks: action.tasks };
42
+ case "SELECT_INDEX":
43
+ return { ...state, selectedIndex: action.index };
44
+ case "SET_VIEW_MODE":
45
+ return { ...state, viewMode: action.mode };
46
+ case "SET_DETAIL_TASK_ID":
47
+ return { ...state, detailTaskId: action.id };
48
+ case "SET_DETAIL_OUTPUT":
49
+ return { ...state, detailOutput: action.output };
50
+ case "RESET_DETAIL":
51
+ return {
52
+ ...state,
53
+ viewMode: "list",
54
+ detailTaskId: null,
55
+ detailOutput: null,
56
+ };
57
+ default:
58
+ return state;
59
+ }
60
+ }
@@ -0,0 +1,35 @@
1
+ export interface MarketplaceAddFormState {
2
+ source: string;
3
+ scopeIndex: number;
4
+ step: "source" | "scope";
5
+ }
6
+
7
+ export type MarketplaceAddFormAction =
8
+ | { type: "SET_SOURCE"; source: string }
9
+ | { type: "SET_SCOPE_INDEX"; index: number }
10
+ | { type: "SET_STEP"; step: "source" | "scope" }
11
+ | { type: "INSERT_CHAR"; text: string }
12
+ | { type: "DELETE_CHAR" }
13
+ | { type: "BACK_TO_SOURCE" };
14
+
15
+ export function marketplaceAddFormReducer(
16
+ state: MarketplaceAddFormState,
17
+ action: MarketplaceAddFormAction,
18
+ ): MarketplaceAddFormState {
19
+ switch (action.type) {
20
+ case "SET_SOURCE":
21
+ return { ...state, source: action.source };
22
+ case "SET_SCOPE_INDEX":
23
+ return { ...state, scopeIndex: action.index };
24
+ case "SET_STEP":
25
+ return { ...state, step: action.step };
26
+ case "INSERT_CHAR":
27
+ return { ...state, source: state.source + action.text };
28
+ case "DELETE_CHAR":
29
+ return { ...state, source: state.source.slice(0, -1) };
30
+ case "BACK_TO_SOURCE":
31
+ return { ...state, step: "source" };
32
+ default:
33
+ return state;
34
+ }
35
+ }
@@ -0,0 +1,31 @@
1
+ export interface McpManagerState {
2
+ selectedIndex: number;
3
+ viewMode: "list" | "detail";
4
+ }
5
+
6
+ export type McpManagerAction =
7
+ | { type: "MOVE_UP"; serverCount: number }
8
+ | { type: "MOVE_DOWN"; serverCount: number }
9
+ | { type: "SET_VIEW_MODE"; viewMode: "list" | "detail" };
10
+
11
+ export function mcpManagerReducer(
12
+ state: McpManagerState,
13
+ action: McpManagerAction,
14
+ ): McpManagerState {
15
+ switch (action.type) {
16
+ case "MOVE_UP":
17
+ return { ...state, selectedIndex: Math.max(0, state.selectedIndex - 1) };
18
+ case "MOVE_DOWN":
19
+ return {
20
+ ...state,
21
+ selectedIndex: Math.min(
22
+ action.serverCount - 1,
23
+ state.selectedIndex + 1,
24
+ ),
25
+ };
26
+ case "SET_VIEW_MODE":
27
+ return { ...state, viewMode: action.viewMode };
28
+ default:
29
+ return state;
30
+ }
31
+ }