wave-code 1.2.0 → 1.3.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 (37) hide show
  1. package/dist/bundle/wave.mjs +1049 -584
  2. package/package.json +4 -3
  3. package/src/cli.tsx +9 -0
  4. package/src/commands/plugin/install.ts +6 -7
  5. package/src/commands/plugin/uninstall.ts +11 -5
  6. package/src/components/ChatInterface.tsx +1 -0
  7. package/src/components/ConfirmationDetails.tsx +36 -15
  8. package/src/components/ConfirmationSelector.tsx +39 -0
  9. package/src/components/InputBox.tsx +39 -2
  10. package/src/components/Markdown.tsx +21 -8
  11. package/src/components/MarketplaceDetail.tsx +10 -27
  12. package/src/components/MarketplaceView.tsx +17 -3
  13. package/src/components/Notifications.tsx +7 -0
  14. package/src/components/PlanView.tsx +22 -17
  15. package/src/components/PluginManagerShell.tsx +3 -3
  16. package/src/components/PluginManagerTypes.ts +2 -1
  17. package/src/components/ResumeCommand.tsx +84 -0
  18. package/src/components/ScrolledContent.tsx +71 -0
  19. package/src/components/SessionSelector.tsx +184 -6
  20. package/src/components/SessionSelectorFlow.tsx +246 -0
  21. package/src/constants/commands.ts +18 -0
  22. package/src/contexts/useChat.tsx +158 -28
  23. package/src/daemon/commands.ts +569 -46
  24. package/src/daemon-cli.ts +6 -0
  25. package/src/hooks/useInputManager.ts +16 -0
  26. package/src/hooks/usePluginManager.ts +104 -78
  27. package/src/index.ts +62 -8
  28. package/src/managers/inputHandlers.ts +14 -9
  29. package/src/managers/inputReducer.ts +29 -9
  30. package/src/reducers/confirmationReducer.ts +20 -1
  31. package/src/reducers/selectorReducer.ts +3 -0
  32. package/src/session-selector-cli.tsx +9 -255
  33. package/src/stdio/agentBridge.ts +397 -95
  34. package/src/stdio/protocol.ts +9 -0
  35. package/src/stdio-cli.ts +6 -0
  36. package/src/utils/sessionOwnership.ts +62 -0
  37. package/src/components/PlanDisplay.tsx +0 -18
@@ -0,0 +1,246 @@
1
+ import React, { useState, useCallback, useEffect, useRef } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import {
4
+ listSessions,
5
+ listAllSessions,
6
+ setSessionCustomTitle,
7
+ truncateContent,
8
+ type SessionMetadata,
9
+ } from "wave-agent-sdk";
10
+ import { SessionSelector } from "./SessionSelector.js";
11
+ import { setClipboardText } from "../utils/clipboard.js";
12
+ import { resolveSessionOwnership } from "../utils/sessionOwnership.js";
13
+
14
+ export interface SessionSelectorResult {
15
+ sessionId: string;
16
+ /**
17
+ * When the selected session belongs to a sibling git worktree of the same
18
+ * repo, the caller should chdir into this path before restoring.
19
+ */
20
+ resumeWorkdir?: string;
21
+ }
22
+
23
+ type SessionItem = SessionMetadata & { firstMessage?: string };
24
+
25
+ /**
26
+ * Interactive session picker.
27
+ *
28
+ * Three scopes, toggled with Ctrl+A / Ctrl+W (mirroring Claude Code's resume
29
+ * picker):
30
+ * - current: sessions in the current working directory (default)
31
+ * - worktrees: sessions in any worktree of the same git repository
32
+ * - all: sessions across every project directory
33
+ *
34
+ * Resume decision on selection:
35
+ * - current dir session → resume in place
36
+ * - same-repo worktree → resume after chdir into that worktree
37
+ * - other project → print `cd <path> && wave --restore <id>`,
38
+ * copy it to the clipboard, and finish
39
+ *
40
+ * Shared by `wave -r` (own Ink render) and the in-TUI `/resume` command, so
41
+ * the two can never drift in scopes or ownership resolution.
42
+ */
43
+ export function SessionSelectorFlow({
44
+ currentWorkdir,
45
+ worktreePaths,
46
+ onDone,
47
+ crossProjectMode = "exit",
48
+ }: {
49
+ currentWorkdir: string;
50
+ worktreePaths: string[];
51
+ onDone: (result: SessionSelectorResult | null) => void;
52
+ /**
53
+ * What happens after the cross-project `cd` command is shown:
54
+ * - "exit": finish the flow (the standalone `wave -r` picker then prints the
55
+ * command and the process exits).
56
+ * - "dismiss": stay in the flow until Esc — used by the in-TUI `/resume`
57
+ * picker, which must not tear down the surrounding conversation.
58
+ */
59
+ crossProjectMode?: "exit" | "dismiss";
60
+ }): React.ReactNode {
61
+ const [showAllProjects, setShowAllProjects] = useState(false);
62
+ const [showAllWorktrees, setShowAllWorktrees] = useState(false);
63
+ const [sessions, setSessions] = useState<SessionItem[]>([]);
64
+ const [loading, setLoading] = useState(true);
65
+ const [crossProjectCommand, setCrossProjectCommand] = useState<string | null>(
66
+ null,
67
+ );
68
+
69
+ // Mirror of the scope flags for use inside async reload/decision callbacks.
70
+ const scopeRef = useRef({ showAllProjects: false, showAllWorktrees: false });
71
+
72
+ const loadSessions = useCallback(
73
+ async (allProjects: boolean, allWorktrees: boolean) => {
74
+ if (allProjects) {
75
+ return listAllSessions();
76
+ }
77
+ if (allWorktrees) {
78
+ return listAllSessions({ worktreePaths, workdir: currentWorkdir });
79
+ }
80
+ return listSessions(currentWorkdir);
81
+ },
82
+ [worktreePaths, currentWorkdir],
83
+ );
84
+
85
+ const reload = useCallback(
86
+ async (allProjects: boolean, allWorktrees: boolean) => {
87
+ setLoading(true);
88
+ try {
89
+ const result = await loadSessions(allProjects, allWorktrees);
90
+ setSessions(
91
+ result.map((s) => ({
92
+ ...s,
93
+ firstMessage: truncateContent(s.firstMessage || "No content", 80),
94
+ })),
95
+ );
96
+ } finally {
97
+ setLoading(false);
98
+ }
99
+ },
100
+ [loadSessions],
101
+ );
102
+
103
+ // Initial load: current directory scope
104
+ useEffect(() => {
105
+ void reload(false, false);
106
+ }, [reload]);
107
+
108
+ const handleToggleAllProjects = useCallback(
109
+ (value: boolean) => {
110
+ scopeRef.current.showAllProjects = value;
111
+ setShowAllProjects(value);
112
+ void reload(value, scopeRef.current.showAllWorktrees);
113
+ },
114
+ [reload],
115
+ );
116
+
117
+ const handleToggleAllWorktrees = useCallback(
118
+ (value: boolean) => {
119
+ scopeRef.current.showAllWorktrees = value;
120
+ setShowAllWorktrees(value);
121
+ void reload(scopeRef.current.showAllProjects, value);
122
+ },
123
+ [reload],
124
+ );
125
+
126
+ const handleSelect = useCallback(
127
+ async (sessionId: string) => {
128
+ const session = sessions.find((s) => s.id === sessionId);
129
+ if (!session) return;
130
+
131
+ const ownership = await resolveSessionOwnership(session, {
132
+ worktreePaths,
133
+ currentWorkdir,
134
+ });
135
+
136
+ switch (ownership.kind) {
137
+ case "current":
138
+ onDone({ sessionId });
139
+ return;
140
+ case "worktree":
141
+ onDone({ sessionId, resumeWorkdir: ownership.resumeWorkdir });
142
+ return;
143
+ case "cross-project":
144
+ // Different project — show the cd command instead of resuming in place
145
+ await setClipboardText(ownership.command);
146
+ setCrossProjectCommand(ownership.command);
147
+ return;
148
+ }
149
+ },
150
+ [sessions, worktreePaths, currentWorkdir, onDone],
151
+ );
152
+
153
+ // Ctrl+R rename. The session may belong to another project (or a sibling
154
+ // worktree), so the write is addressed by the session's own workdir — the
155
+ // SDK falls back to scanning every project directory when that path no
156
+ // longer matches. Rejections bubble up so the picker can show the failure.
157
+ const handleRename = useCallback(
158
+ async (sessionId: string, title: string) => {
159
+ const session = sessions.find((s) => s.id === sessionId);
160
+ await setSessionCustomTitle(
161
+ sessionId,
162
+ session?.workdir ?? currentWorkdir,
163
+ title,
164
+ );
165
+ },
166
+ [sessions, currentWorkdir],
167
+ );
168
+
169
+ if (crossProjectCommand) {
170
+ return (
171
+ <CrossProjectMessage
172
+ command={crossProjectCommand}
173
+ onExit={() =>
174
+ crossProjectMode === "dismiss"
175
+ ? setCrossProjectCommand(null)
176
+ : onDone(null)
177
+ }
178
+ autoExit={crossProjectMode === "exit"}
179
+ />
180
+ );
181
+ }
182
+
183
+ if (loading) {
184
+ return (
185
+ <Box>
186
+ <Text dimColor>Loading conversations…</Text>
187
+ </Box>
188
+ );
189
+ }
190
+
191
+ return (
192
+ <Box padding={1}>
193
+ <SessionSelector
194
+ sessions={sessions}
195
+ worktreePaths={worktreePaths}
196
+ showProjectPath={showAllProjects || showAllWorktrees}
197
+ onSelect={(id) => void handleSelect(id)}
198
+ onCancel={() => onDone(null)}
199
+ onToggleAllProjects={handleToggleAllProjects}
200
+ onToggleAllWorktrees={handleToggleAllWorktrees}
201
+ onRename={handleRename}
202
+ />
203
+ </Box>
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Cross-project notice. `wave -r` auto-exits after a short delay (mirrors
209
+ * Claude Code's resume picker, which prints the cd command and exits); the
210
+ * in-TUI `/resume` picker waits for Esc instead, so the conversation behind it
211
+ * survives.
212
+ */
213
+ function CrossProjectMessage({
214
+ command,
215
+ onExit,
216
+ autoExit,
217
+ }: {
218
+ command: string;
219
+ onExit: () => void;
220
+ autoExit: boolean;
221
+ }): React.ReactNode {
222
+ useEffect(() => {
223
+ if (!autoExit) return;
224
+ const timer = setTimeout(onExit, 100);
225
+ return () => clearTimeout(timer);
226
+ }, [autoExit, onExit]);
227
+
228
+ useInput((_input, key) => {
229
+ if (!autoExit && key.escape) {
230
+ onExit();
231
+ }
232
+ });
233
+
234
+ return (
235
+ <Box flexDirection="column" gap={1}>
236
+ <Text>This conversation is from a different directory.</Text>
237
+ <Box flexDirection="column">
238
+ <Text>To resume, run:</Text>
239
+ <Text> {command}</Text>
240
+ </Box>
241
+ <Text dimColor>
242
+ {autoExit ? "(Command copied to clipboard)" : "Esc to go back"}
243
+ </Text>
244
+ </Box>
245
+ );
246
+ }
@@ -56,6 +56,12 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
56
56
  description: "View and manage plugins",
57
57
  handler: () => {}, // Handler here won't be used, actual processing is in the hook
58
58
  },
59
+ {
60
+ id: "reload-plugins",
61
+ name: "reload-plugins",
62
+ description: "Reload plugins in place, without restarting the session",
63
+ handler: () => {}, // Handler here won't be used, actual processing is in the hook
64
+ },
59
65
  {
60
66
  id: "btw",
61
67
  name: "btw",
@@ -92,6 +98,18 @@ export const AVAILABLE_COMMANDS: SlashCommand[] = [
92
98
  description: "Enable plan mode or view the current session plan",
93
99
  handler: () => {}, // Handler here won't be used, actual processing is in the hook
94
100
  },
101
+ {
102
+ id: "resume",
103
+ name: "resume",
104
+ description: "Switch to another conversation without leaving the session",
105
+ handler: () => {}, // Handler here won't be used, actual processing is in the hook
106
+ },
107
+ {
108
+ id: "rename",
109
+ name: "rename",
110
+ description: "Set a custom title for the current conversation",
111
+ handler: () => {}, // Handler here won't be used, actual processing is in the hook
112
+ },
95
113
  {
96
114
  id: "clear",
97
115
  name: "clear",
@@ -31,6 +31,8 @@ import {
31
31
  type WorktreeSession,
32
32
  OPERATION_CANCELLED_BY_USER,
33
33
  extractLatestTotalTokens,
34
+ PLUGIN_CHANGE_PENDING_MESSAGE,
35
+ PLUGIN_RELOADED_MESSAGE,
34
36
  } from "wave-agent-sdk";
35
37
  import { logger } from "../utils/logger.js";
36
38
  import { displayUsageSummary } from "../utils/usageSummary.js";
@@ -77,6 +79,17 @@ export interface ChatContextType {
77
79
  clearMessages: () => Promise<void>;
78
80
  compact: (instructions?: string) => Promise<void>;
79
81
  addDir: (args?: string) => Promise<void>;
82
+ /**
83
+ * Set the current session's custom title (`/rename <title>`). A blank title
84
+ * only prints the usage hint — no AI-generated titles (spec: session-management.md).
85
+ */
86
+ renameSession: (args?: string) => Promise<void>;
87
+ /**
88
+ * Switch to another conversation in-process (`/resume`). Pass `resumeWorkdir`
89
+ * when the target session lives in a sibling worktree of the same repo — the
90
+ * session then moves into that directory too.
91
+ */
92
+ resumeSession: (sessionId: string, resumeWorkdir?: string) => Promise<void>;
80
93
  abortMessage: () => void;
81
94
  recallQueuedMessage: () => QueuedMessage | null;
82
95
  removeQueuedMessageById: (id: string) => boolean;
@@ -131,6 +144,7 @@ export interface ChatContextType {
131
144
  input?: Record<string, unknown>;
132
145
  suggestedPrefix?: string;
133
146
  hidePersistentOption?: boolean;
147
+ outsideSafeZoneDirectory?: string;
134
148
  planContent?: string;
135
149
  permissionMode?: PermissionMode;
136
150
  warning?: string;
@@ -143,6 +157,7 @@ export interface ChatContextType {
143
157
  planContent?: string,
144
158
  permissionMode?: PermissionMode,
145
159
  warning?: string,
160
+ outsideSafeZoneDirectory?: string,
146
161
  ) => Promise<PermissionDecision>;
147
162
  hideConfirmation: () => void;
148
163
  handleConfirmationDecision: (decision: PermissionDecision) => void;
@@ -164,8 +179,10 @@ export interface ChatContextType {
164
179
  workingDirectory: string;
165
180
  version?: string;
166
181
  workdir?: string;
167
- // Agent recreation (e.g. after plugin install)
168
- recreateAgent: () => void;
182
+ // Plugin in-place reload (docs/specs/ecosystem/plugin.md「插件变更的就地重载」)
183
+ reloadPlugins: () => Promise<void>;
184
+ notifyPluginChange: () => void;
185
+ pluginHint: string | null;
169
186
  }
170
187
 
171
188
  const ChatContext = createContext<ChatContextType | null>(null);
@@ -512,6 +529,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
512
529
  input?: Record<string, unknown>;
513
530
  suggestedPrefix?: string;
514
531
  hidePersistentOption?: boolean;
532
+ outsideSafeZoneDirectory?: string;
515
533
  planContent?: string;
516
534
  permissionMode?: PermissionMode;
517
535
  warning?: string;
@@ -524,6 +542,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
524
542
  toolInput?: Record<string, unknown>;
525
543
  suggestedPrefix?: string;
526
544
  hidePersistentOption?: boolean;
545
+ outsideSafeZoneDirectory?: string;
527
546
  planContent?: string;
528
547
  permissionMode?: PermissionMode;
529
548
  warning?: string;
@@ -536,6 +555,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
536
555
  toolInput?: Record<string, unknown>;
537
556
  suggestedPrefix?: string;
538
557
  hidePersistentOption?: boolean;
558
+ outsideSafeZoneDirectory?: string;
539
559
  planContent?: string;
540
560
  permissionMode?: PermissionMode;
541
561
  warning?: string;
@@ -592,6 +612,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
592
612
  planContent?: string,
593
613
  permissionMode?: PermissionMode,
594
614
  warning?: string,
615
+ outsideSafeZoneDirectory?: string,
595
616
  ): Promise<PermissionDecision> => {
596
617
  return new Promise<PermissionDecision>((resolve, reject) => {
597
618
  const queueItem = {
@@ -599,6 +620,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
599
620
  toolInput,
600
621
  suggestedPrefix,
601
622
  hidePersistentOption,
623
+ outsideSafeZoneDirectory,
602
624
  planContent,
603
625
  permissionMode,
604
626
  warning,
@@ -775,6 +797,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
775
797
  context.planContent,
776
798
  context.permissionMode,
777
799
  context.warning,
800
+ context.outsideSafeZoneDirectory,
778
801
  );
779
802
  } catch {
780
803
  // If confirmation was cancelled or failed, deny the operation
@@ -892,32 +915,50 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
892
915
  ],
893
916
  );
894
917
 
895
- // Recreate agent (e.g. after plugin install) — destroys current agent and reinitializes
896
- const recreateAgent = useCallback(() => {
897
- const currentSessionId = agentRef.current?.sessionId;
898
- if (agentRef.current) {
899
- try {
900
- agentRef.current.destroy();
901
- } catch {
902
- // Ignore destroy errors
903
- }
918
+ // 插件变更落盘后只置一个「待应用」信号并提示一次,不再重建 Agent;真正换装由
919
+ // 用户敲 /reload-plugins 触发,在当前会话内完成(docs/specs/ecosystem/plugin.md
920
+ // 「插件变更的就地重载」:会…不重建、不打断回合、不新建会话)。
921
+ const [pluginHint, setPluginHint] = useState<string | null>(null);
922
+ const pluginHintTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
923
+
924
+ const showPluginHint = useCallback((message: string) => {
925
+ setPluginHint(message);
926
+ if (pluginHintTimer.current) {
927
+ clearTimeout(pluginHintTimer.current);
904
928
  }
905
- agentRef.current = null;
906
- setMessages([]);
907
- setMcpServerStatuses([]);
908
- setSlashCommands([]);
909
- setAgentDefinitions([]);
910
- setSkills([]);
911
- setSessionId("");
912
- setIsLoading(false);
913
- setLatestTotalTokens(0);
914
- setMaxInputTokens(200000);
915
- setIsCommandRunning(false);
916
- setIsCompacting(false);
917
- if (currentSessionId) {
918
- initializeAgent(currentSessionId);
929
+ // 一次性提示:数秒后自行消失,不留常驻标识(spec「插件变更提示」)。
930
+ pluginHintTimer.current = setTimeout(() => setPluginHint(null), 8000);
931
+ }, []);
932
+
933
+ const notifyPluginChange = useCallback(() => {
934
+ showPluginHint(PLUGIN_CHANGE_PENDING_MESSAGE);
935
+ }, [showPluginHint]);
936
+
937
+ const reloadPlugins = useCallback(async () => {
938
+ const agent = agentRef.current;
939
+ if (!agent) return;
940
+ try {
941
+ const result = await agent.reloadPlugins();
942
+ // 换装后本进程各会话的能力已变,UI 侧的命令 / 子代理 / 技能列表同步重读。
943
+ setSlashCommands(agent.getSlashCommands?.() || []);
944
+ setAgentDefinitions(agent.getSubagentConfigurations?.() || []);
945
+ setSkills(agent.getSkillMetadata?.() || []);
946
+ if (result.failures.length > 0) {
947
+ // 失败不回滚:如实提示,会话继续可用(spec 场景 11)。
948
+ showPluginHint(
949
+ `${PLUGIN_RELOADED_MESSAGE}部分插件加载失败:${result.failures
950
+ .map((failure) => `${failure.path}: ${failure.error}`)
951
+ .join(";")}`,
952
+ );
953
+ } else {
954
+ showPluginHint(PLUGIN_RELOADED_MESSAGE);
955
+ }
956
+ } catch (error) {
957
+ showPluginHint(
958
+ `插件重载失败:${error instanceof Error ? error.message : String(error)}`,
959
+ );
919
960
  }
920
- }, [initializeAgent]);
961
+ }, [showPluginHint]);
921
962
 
922
963
  // Run initial agent creation
923
964
  useEffect(() => {
@@ -1061,6 +1102,90 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
1061
1102
  );
1062
1103
  }, []);
1063
1104
 
1105
+ // /rename: set the current conversation's custom title. The title is stored
1106
+ // as a reserved `custom-title` entry appended to the session's own JSONL
1107
+ // file, so `wave -r` and every GUI host read the same value. A blank title
1108
+ // only prints the usage hint — no AI-generated titles (spec:
1109
+ // session-management.md "不做 AI 自动标题").
1110
+ const renameSession = useCallback(async (args?: string) => {
1111
+ const agent = agentRef.current;
1112
+ if (!agent) return;
1113
+
1114
+ const title = (args ?? "").trim();
1115
+ const showResult = (content: string) => {
1116
+ setMessages((prev) => [
1117
+ ...prev,
1118
+ {
1119
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
1120
+ role: "assistant",
1121
+ timestamp: new Date().toISOString(),
1122
+ blocks: [{ type: "text", content, stage: "end" }],
1123
+ },
1124
+ ]);
1125
+ };
1126
+
1127
+ if (!title) {
1128
+ showResult("Usage: /rename <title>");
1129
+ return;
1130
+ }
1131
+
1132
+ try {
1133
+ await agent.renameSession(title);
1134
+ showResult(`Conversation renamed to "${title}".`);
1135
+ } catch (error) {
1136
+ showResult(
1137
+ `Failed to rename conversation: ${
1138
+ error instanceof Error ? error.message : String(error)
1139
+ }`,
1140
+ );
1141
+ }
1142
+ }, []);
1143
+
1144
+ // /resume: switch to another conversation without leaving the process. The
1145
+ // session id and its on-disk project directory move together, so the next
1146
+ // message is appended to the target's own transcript. A failed restore leaves
1147
+ // the current session untouched and only surfaces a notice.
1148
+ const resumeSession = useCallback(
1149
+ async (sessionId: string, resumeWorkdir?: string) => {
1150
+ const agent = agentRef.current;
1151
+ if (!agent) return;
1152
+
1153
+ try {
1154
+ await agent.restoreSession(
1155
+ sessionId,
1156
+ resumeWorkdir ? { workdir: resumeWorkdir } : undefined,
1157
+ );
1158
+ } catch (error) {
1159
+ // UI-only notice (same display path as /add-dir): nothing was switched,
1160
+ // so nothing about the failed target is persisted.
1161
+ setMessages((prev) => [
1162
+ ...prev,
1163
+ {
1164
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
1165
+ role: "assistant",
1166
+ timestamp: new Date().toISOString(),
1167
+ blocks: [
1168
+ {
1169
+ type: "error",
1170
+ content: `Failed to resume conversation: ${
1171
+ error instanceof Error ? error.message : String(error)
1172
+ }`,
1173
+ },
1174
+ ],
1175
+ },
1176
+ ]);
1177
+ return;
1178
+ }
1179
+
1180
+ setSessionId(agent.sessionId);
1181
+ setWorkingDirectory(agent.workingDirectory);
1182
+ setIsLoading(agent.isLoading);
1183
+ refreshMessages();
1184
+ forceRemount();
1185
+ },
1186
+ [forceRemount, refreshMessages],
1187
+ );
1188
+
1064
1189
  // Unified interrupt method, interrupt both AI messages and command execution
1065
1190
  const abortMessage = useCallback(() => {
1066
1191
  agentRef.current?.abortMessage();
@@ -1170,6 +1295,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
1170
1295
  input: next.toolInput,
1171
1296
  suggestedPrefix: next.suggestedPrefix,
1172
1297
  hidePersistentOption: next.hidePersistentOption,
1298
+ outsideSafeZoneDirectory: next.outsideSafeZoneDirectory,
1173
1299
  planContent: next.planContent,
1174
1300
  permissionMode: next.permissionMode,
1175
1301
  warning: next.warning,
@@ -1257,7 +1383,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
1257
1383
 
1258
1384
  const setModel = useCallback((model: string) => {
1259
1385
  if (agentRef.current) {
1260
- agentRef.current.setModel(model);
1386
+ void agentRef.current.setModel(model);
1261
1387
  setCurrentModelState(model);
1262
1388
  }
1263
1389
  }, []);
@@ -1307,6 +1433,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
1307
1433
  clearMessages,
1308
1434
  compact,
1309
1435
  addDir,
1436
+ renameSession,
1437
+ resumeSession,
1310
1438
  abortMessage,
1311
1439
  recallQueuedMessage,
1312
1440
  removeQueuedMessageById,
@@ -1355,7 +1483,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
1355
1483
  workingDirectory,
1356
1484
  version,
1357
1485
  workdir,
1358
- recreateAgent,
1486
+ reloadPlugins,
1487
+ notifyPluginChange,
1488
+ pluginHint,
1359
1489
  };
1360
1490
 
1361
1491
  return (