@mseep/obsidian-agent-client 0.10.6

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 (146) hide show
  1. package/.claude/hooks/gh-setup.sh +49 -0
  2. package/.claude/settings.json +15 -0
  3. package/.claude/skills/release-notes/SKILL.md +331 -0
  4. package/.editorconfig +10 -0
  5. package/.github/FUNDING.yml +2 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +59 -0
  9. package/.github/copilot-instructions.md +45 -0
  10. package/.github/pull_request_template.md +32 -0
  11. package/.github/workflows/ci.yaml +25 -0
  12. package/.github/workflows/docs.yml +58 -0
  13. package/.github/workflows/relay_to_openclaw.yml +59 -0
  14. package/.github/workflows/release.yaml +45 -0
  15. package/.prettierignore +10 -0
  16. package/.prettierrc +13 -0
  17. package/.vscode/extensions.json +7 -0
  18. package/.vscode/settings.json +37 -0
  19. package/.zed/settings.json +42 -0
  20. package/AGENTS.md +330 -0
  21. package/ARCHITECTURE.md +390 -0
  22. package/CONTRIBUTING.md +216 -0
  23. package/LICENSE +202 -0
  24. package/NOTICE +2 -0
  25. package/README.ja.md +121 -0
  26. package/README.md +125 -0
  27. package/docs/.vitepress/config.mts +124 -0
  28. package/docs/.vitepress/theme/custom.css +111 -0
  29. package/docs/.vitepress/theme/index.ts +4 -0
  30. package/docs/agent-setup/claude-code.md +84 -0
  31. package/docs/agent-setup/codex.md +76 -0
  32. package/docs/agent-setup/custom-agents.md +67 -0
  33. package/docs/agent-setup/gemini-cli.md +99 -0
  34. package/docs/agent-setup/index.md +34 -0
  35. package/docs/announcements/gemini-cli-deprecation.md +73 -0
  36. package/docs/getting-started/index.md +78 -0
  37. package/docs/getting-started/quick-start.md +38 -0
  38. package/docs/help/faq.md +181 -0
  39. package/docs/help/troubleshooting.md +221 -0
  40. package/docs/index.md +63 -0
  41. package/docs/public/apple-touch-icon.png +0 -0
  42. package/docs/public/demo.mp4 +0 -0
  43. package/docs/public/favicon-16x16.png +0 -0
  44. package/docs/public/favicon-32x32.png +0 -0
  45. package/docs/public/favicon.ico +0 -0
  46. package/docs/public/images/editing.webp +0 -0
  47. package/docs/public/images/export.webp +0 -0
  48. package/docs/public/images/floating-chat-button.webp +0 -0
  49. package/docs/public/images/floating-chat-instance-menu.webp +0 -0
  50. package/docs/public/images/floating-chat-view.webp +0 -0
  51. package/docs/public/images/mode-selection.webp +0 -0
  52. package/docs/public/images/model-selection.webp +0 -0
  53. package/docs/public/images/multi-session.webp +0 -0
  54. package/docs/public/images/remove-image.webp +0 -0
  55. package/docs/public/images/ribbon-icon.webp +0 -0
  56. package/docs/public/images/selection-context.gif +0 -0
  57. package/docs/public/images/sending-images.webp +0 -0
  58. package/docs/public/images/sending-messages.webp +0 -0
  59. package/docs/public/images/session-history-button.webp +0 -0
  60. package/docs/public/images/slash-commands-1.webp +0 -0
  61. package/docs/public/images/slash-commands-2.webp +0 -0
  62. package/docs/public/images/switch-agent.webp +0 -0
  63. package/docs/public/images/switch-default-agent.webp +0 -0
  64. package/docs/public/images/temporary-disable.gif +0 -0
  65. package/docs/reference/acp-support.md +110 -0
  66. package/docs/usage/chat-export.md +80 -0
  67. package/docs/usage/commands.md +51 -0
  68. package/docs/usage/context-files.md +57 -0
  69. package/docs/usage/editing.md +69 -0
  70. package/docs/usage/floating-chat.md +84 -0
  71. package/docs/usage/index.md +97 -0
  72. package/docs/usage/mcp-tools.md +33 -0
  73. package/docs/usage/mentions.md +70 -0
  74. package/docs/usage/mode-selection.md +28 -0
  75. package/docs/usage/model-selection.md +32 -0
  76. package/docs/usage/multi-session.md +68 -0
  77. package/docs/usage/sending-images.md +64 -0
  78. package/docs/usage/session-history.md +91 -0
  79. package/docs/usage/slash-commands.md +44 -0
  80. package/esbuild.config.mjs +49 -0
  81. package/eslint.config.mjs +25 -0
  82. package/main.js +228 -0
  83. package/manifest.json +11 -0
  84. package/package.json +52 -0
  85. package/src/acp/acp-client.ts +921 -0
  86. package/src/acp/acp-handler.ts +252 -0
  87. package/src/acp/permission-handler.ts +282 -0
  88. package/src/acp/terminal-handler.ts +264 -0
  89. package/src/acp/type-converter.ts +272 -0
  90. package/src/hooks/useAgent.ts +250 -0
  91. package/src/hooks/useAgentMessages.ts +470 -0
  92. package/src/hooks/useAgentSession.ts +544 -0
  93. package/src/hooks/useChatActions.ts +400 -0
  94. package/src/hooks/useHistoryModal.ts +219 -0
  95. package/src/hooks/useSessionHistory.ts +863 -0
  96. package/src/hooks/useSettings.ts +19 -0
  97. package/src/hooks/useSuggestions.ts +342 -0
  98. package/src/main.ts +9 -0
  99. package/src/plugin.ts +1126 -0
  100. package/src/services/chat-exporter.ts +552 -0
  101. package/src/services/message-sender.ts +755 -0
  102. package/src/services/message-state.ts +375 -0
  103. package/src/services/session-helpers.ts +211 -0
  104. package/src/services/session-state.ts +130 -0
  105. package/src/services/session-storage.ts +267 -0
  106. package/src/services/settings-normalizer.ts +255 -0
  107. package/src/services/settings-service.ts +285 -0
  108. package/src/services/update-checker.ts +128 -0
  109. package/src/services/vault-service.ts +558 -0
  110. package/src/services/view-registry.ts +345 -0
  111. package/src/types/agent.ts +92 -0
  112. package/src/types/chat.ts +351 -0
  113. package/src/types/errors.ts +136 -0
  114. package/src/types/obsidian-internals.d.ts +14 -0
  115. package/src/types/session.ts +731 -0
  116. package/src/ui/ChangeDirectoryModal.ts +137 -0
  117. package/src/ui/ChatContext.ts +25 -0
  118. package/src/ui/ChatHeader.tsx +295 -0
  119. package/src/ui/ChatPanel.tsx +1162 -0
  120. package/src/ui/ChatView.tsx +348 -0
  121. package/src/ui/ErrorBanner.tsx +104 -0
  122. package/src/ui/FloatingButton.tsx +351 -0
  123. package/src/ui/FloatingChatView.tsx +531 -0
  124. package/src/ui/InputArea.tsx +1107 -0
  125. package/src/ui/InputToolbar.tsx +371 -0
  126. package/src/ui/MessageBubble.tsx +442 -0
  127. package/src/ui/MessageList.tsx +265 -0
  128. package/src/ui/PermissionBanner.tsx +61 -0
  129. package/src/ui/SessionHistoryModal.tsx +821 -0
  130. package/src/ui/SettingsTab.ts +1337 -0
  131. package/src/ui/SuggestionPopup.tsx +138 -0
  132. package/src/ui/TerminalBlock.tsx +107 -0
  133. package/src/ui/ToolCallBlock.tsx +456 -0
  134. package/src/ui/shared/AttachmentStrip.tsx +57 -0
  135. package/src/ui/shared/IconButton.tsx +55 -0
  136. package/src/ui/shared/MarkdownRenderer.tsx +103 -0
  137. package/src/ui/view-host.ts +56 -0
  138. package/src/utils/error-utils.ts +274 -0
  139. package/src/utils/logger.ts +44 -0
  140. package/src/utils/mention-parser.ts +129 -0
  141. package/src/utils/paths.ts +246 -0
  142. package/src/utils/platform.ts +425 -0
  143. package/styles.css +2322 -0
  144. package/tsconfig.json +18 -0
  145. package/version-bump.mjs +18 -0
  146. package/versions.json +3 -0
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Hook for ChatPanel business callbacks.
3
+ *
4
+ * Encapsulates message sending, new chat, export, agent switching/restart,
5
+ * config changes, and related UI state (restoredMessage, agentUpdateNotification).
6
+ */
7
+
8
+ import { useState, useCallback } from "react";
9
+ import { Notice, Platform } from "obsidian";
10
+
11
+ import type AgentClientPlugin from "../plugin";
12
+ import type { UseAgentReturn } from "./useAgent";
13
+ import type { UseSessionHistoryReturn } from "./useSessionHistory";
14
+ import type { UseSuggestionsReturn } from "./useSuggestions";
15
+ import type { ChatSession } from "../types/session";
16
+ import type {
17
+ ChatMessage,
18
+ AttachedFile,
19
+ ImagePromptContent,
20
+ ResourceLinkPromptContent,
21
+ } from "../types/chat";
22
+ import type { AgentClientPluginSettings } from "../plugin";
23
+ import type { AgentUpdateNotification } from "../services/update-checker";
24
+ import { ChatExporter } from "../services/chat-exporter";
25
+ import { getLogger } from "../utils/logger";
26
+ import { buildFileUri } from "../utils/paths";
27
+ import { convertWindowsPathToWsl } from "../utils/platform";
28
+
29
+ // ============================================================================
30
+ // Types
31
+ // ============================================================================
32
+
33
+ export interface UseChatActionsReturn {
34
+ // Message actions
35
+ handleSendMessage: (
36
+ content: string,
37
+ attachments?: AttachedFile[],
38
+ ) => Promise<void>;
39
+ handleStopGeneration: () => Promise<void>;
40
+ handleNewChat: (requestedAgentId?: string) => Promise<void>;
41
+ handleExportChat: () => Promise<void>;
42
+ handleSwitchAgent: (agentId: string) => Promise<void>;
43
+ handleRestartAgent: () => Promise<void>;
44
+
45
+ // Config actions
46
+ handleSetMode: (modeId: string) => Promise<void>;
47
+ handleSetModel: (modelId: string) => Promise<void>;
48
+ handleSetConfigOption: (configId: string, value: string) => Promise<void>;
49
+
50
+ // UI state actions
51
+ handleClearError: () => void;
52
+ handleClearAgentUpdate: () => void;
53
+ handleRestoredMessageConsumed: () => void;
54
+
55
+ // State (moved from ChatPanel)
56
+ restoredMessage: string | null;
57
+ agentUpdateNotification: AgentUpdateNotification | null;
58
+ setAgentUpdateNotification: (n: AgentUpdateNotification | null) => void;
59
+
60
+ // Auto-export (needed by ChatPanel cleanup)
61
+ autoExportIfEnabled: (
62
+ trigger: "newChat" | "closeChat",
63
+ triggerMessages: ChatMessage[],
64
+ triggerSession: ChatSession,
65
+ ) => Promise<void>;
66
+ }
67
+
68
+ // ============================================================================
69
+ // Hook Implementation
70
+ // ============================================================================
71
+
72
+ export function useChatActions(
73
+ plugin: AgentClientPlugin,
74
+ agent: UseAgentReturn,
75
+ sessionHistory: UseSessionHistoryReturn,
76
+ suggestions: UseSuggestionsReturn,
77
+ session: ChatSession,
78
+ messages: ChatMessage[],
79
+ settings: AgentClientPluginSettings,
80
+ vaultPath: string,
81
+ ): UseChatActionsReturn {
82
+ const logger = getLogger();
83
+
84
+ // ============================================================
85
+ // State (moved from ChatPanel)
86
+ // ============================================================
87
+
88
+ const [restoredMessage, setRestoredMessage] = useState<string | null>(null);
89
+ const [agentUpdateNotification, setAgentUpdateNotification] =
90
+ useState<AgentUpdateNotification | null>(null);
91
+
92
+ // ============================================================
93
+ // Auto-export
94
+ // ============================================================
95
+
96
+ const autoExportIfEnabled = useCallback(
97
+ async (
98
+ trigger: "newChat" | "closeChat",
99
+ triggerMessages: ChatMessage[],
100
+ triggerSession: ChatSession,
101
+ ): Promise<void> => {
102
+ const isEnabled =
103
+ trigger === "newChat"
104
+ ? plugin.settings.exportSettings.autoExportOnNewChat
105
+ : plugin.settings.exportSettings.autoExportOnCloseChat;
106
+ if (!isEnabled) return;
107
+ if (triggerMessages.length === 0) return;
108
+ if (!triggerSession.sessionId) return;
109
+
110
+ try {
111
+ const exporter = new ChatExporter(plugin);
112
+ const openFile =
113
+ plugin.settings.exportSettings.openFileAfterExport;
114
+ const filePath = await exporter.exportToMarkdown(
115
+ triggerMessages,
116
+ triggerSession.agentDisplayName,
117
+ triggerSession.agentId,
118
+ triggerSession.sessionId,
119
+ triggerSession.createdAt,
120
+ openFile,
121
+ );
122
+ if (filePath) {
123
+ const context =
124
+ trigger === "newChat" ? "new session" : "closing chat";
125
+ new Notice(`[Agent Client] Chat exported to ${filePath}`);
126
+ logger.log(`Chat auto-exported before ${context}`);
127
+ }
128
+ } catch {
129
+ new Notice("[Agent Client] Failed to export chat");
130
+ }
131
+ },
132
+ [plugin, logger],
133
+ );
134
+
135
+ // ============================================================
136
+ // Message Actions
137
+ // ============================================================
138
+
139
+ const shouldConvertToWsl = Platform.isWin && settings.windowsWslMode;
140
+
141
+ const handleSendMessage = useCallback(
142
+ async (content: string, attachments?: AttachedFile[]) => {
143
+ // Dismiss overlays on send
144
+ agent.clearError();
145
+ setAgentUpdateNotification(null);
146
+
147
+ const isFirstMessage = messages.length === 0;
148
+
149
+ // Split attachments by kind
150
+ const images: ImagePromptContent[] = [];
151
+ const resourceLinks: ResourceLinkPromptContent[] = [];
152
+
153
+ if (attachments) {
154
+ for (const file of attachments) {
155
+ if (file.kind === "image" && file.data) {
156
+ images.push({
157
+ type: "image",
158
+ data: file.data,
159
+ mimeType: file.mimeType,
160
+ });
161
+ } else if (file.kind === "file" && file.path) {
162
+ let filePath = file.path;
163
+ if (shouldConvertToWsl) {
164
+ filePath = convertWindowsPathToWsl(filePath);
165
+ }
166
+ resourceLinks.push({
167
+ type: "resource_link",
168
+ uri: buildFileUri(filePath),
169
+ name:
170
+ file.name ??
171
+ file.path.split("/").pop() ??
172
+ "file",
173
+ mimeType: file.mimeType || undefined,
174
+ size: file.size,
175
+ });
176
+ }
177
+ }
178
+ }
179
+
180
+ await agent.sendMessage(content, {
181
+ activeNote: settings.autoMentionActiveNote
182
+ ? suggestions.mentions.activeNote
183
+ : null,
184
+ vaultBasePath: vaultPath,
185
+ isAutoMentionDisabled:
186
+ suggestions.mentions.isAutoMentionDisabled,
187
+ images: images.length > 0 ? images : undefined,
188
+ resourceLinks:
189
+ resourceLinks.length > 0 ? resourceLinks : undefined,
190
+ });
191
+
192
+ // Save session metadata locally on first message
193
+ if (isFirstMessage && session.sessionId) {
194
+ await sessionHistory.saveSessionLocally(
195
+ session.sessionId,
196
+ content,
197
+ );
198
+ logger.log(
199
+ `[ChatPanel] Session saved locally: ${session.sessionId}`,
200
+ );
201
+ }
202
+ },
203
+ [
204
+ agent.clearError,
205
+ agent.sendMessage,
206
+ messages.length,
207
+ session.sessionId,
208
+ sessionHistory.saveSessionLocally,
209
+ logger,
210
+ settings.autoMentionActiveNote,
211
+ suggestions.mentions.activeNote,
212
+ suggestions.mentions.isAutoMentionDisabled,
213
+ shouldConvertToWsl,
214
+ vaultPath,
215
+ ],
216
+ );
217
+
218
+ const handleStopGeneration = useCallback(async () => {
219
+ logger.log("Cancelling current operation...");
220
+ const lastMessage = agent.lastUserMessage;
221
+ await agent.cancelOperation();
222
+ if (lastMessage) {
223
+ setRestoredMessage(lastMessage);
224
+ }
225
+ }, [logger, agent.cancelOperation, agent.lastUserMessage]);
226
+
227
+ const handleNewChat = useCallback(
228
+ async (requestedAgentId?: string) => {
229
+ const isAgentSwitch =
230
+ requestedAgentId && requestedAgentId !== session.agentId;
231
+
232
+ // Skip if already empty AND not switching agents
233
+ if (messages.length === 0 && !isAgentSwitch) {
234
+ new Notice("[Agent Client] Already a new session");
235
+ return;
236
+ }
237
+
238
+ // Cancel ongoing generation before starting new chat
239
+ if (agent.isSending) {
240
+ await agent.cancelOperation();
241
+ }
242
+
243
+ logger.log(
244
+ `[Debug] Creating new session${isAgentSwitch ? ` with agent: ${requestedAgentId}` : ""}...`,
245
+ );
246
+
247
+ // Auto-export current chat before starting new one (if has messages)
248
+ if (messages.length > 0) {
249
+ await autoExportIfEnabled("newChat", messages, session);
250
+ }
251
+
252
+ suggestions.mentions.toggleAutoMention(false);
253
+ agent.clearMessages();
254
+
255
+ const newAgentId = isAgentSwitch
256
+ ? requestedAgentId
257
+ : session.agentId;
258
+ await agent.restartSession(newAgentId);
259
+
260
+ // Invalidate session history cache when creating new session
261
+ sessionHistory.invalidateCache();
262
+ },
263
+ [
264
+ messages,
265
+ session,
266
+ logger,
267
+ autoExportIfEnabled,
268
+ agent.isSending,
269
+ agent.cancelOperation,
270
+ agent.clearMessages,
271
+ agent.restartSession,
272
+ suggestions.mentions.toggleAutoMention,
273
+ sessionHistory.invalidateCache,
274
+ ],
275
+ );
276
+
277
+ const handleExportChat = useCallback(async () => {
278
+ if (messages.length === 0) {
279
+ new Notice("[Agent Client] No messages to export");
280
+ return;
281
+ }
282
+
283
+ try {
284
+ const exporter = new ChatExporter(plugin);
285
+ const openFile = plugin.settings.exportSettings.openFileAfterExport;
286
+ const filePath = await exporter.exportToMarkdown(
287
+ messages,
288
+ session.agentDisplayName,
289
+ session.agentId,
290
+ session.sessionId || "unknown",
291
+ session.createdAt,
292
+ openFile,
293
+ );
294
+ new Notice(`[Agent Client] Chat exported to ${filePath}`);
295
+ } catch (error) {
296
+ new Notice("[Agent Client] Failed to export chat");
297
+ logger.error("Export error:", error);
298
+ }
299
+ }, [messages, session, plugin, logger]);
300
+
301
+ const handleSwitchAgent = useCallback(
302
+ async (agentId: string) => {
303
+ if (agentId !== session.agentId) {
304
+ await handleNewChat(agentId);
305
+ }
306
+ },
307
+ [session.agentId, handleNewChat],
308
+ );
309
+
310
+ const handleRestartAgent = useCallback(async () => {
311
+ logger.log("[ChatPanel] Restarting agent process...");
312
+
313
+ // Auto-export current chat before restart (if has messages)
314
+ if (messages.length > 0) {
315
+ await autoExportIfEnabled("newChat", messages, session);
316
+ }
317
+
318
+ // Clear messages for fresh start
319
+ agent.clearMessages();
320
+
321
+ try {
322
+ await agent.forceRestartAgent();
323
+ new Notice("[Agent Client] Agent restarted");
324
+ } catch (error) {
325
+ new Notice("[Agent Client] Failed to restart agent");
326
+ logger.error("Restart error:", error);
327
+ }
328
+ }, [
329
+ logger,
330
+ messages,
331
+ session,
332
+ autoExportIfEnabled,
333
+ agent.clearMessages,
334
+ agent.forceRestartAgent,
335
+ ]);
336
+
337
+ // ============================================================
338
+ // Config Actions
339
+ // ============================================================
340
+
341
+ const handleSetMode = useCallback(
342
+ async (modeId: string) => {
343
+ await agent.setMode(modeId);
344
+ },
345
+ [agent.setMode],
346
+ );
347
+
348
+ const handleSetModel = useCallback(
349
+ async (modelId: string) => {
350
+ await agent.setModel(modelId);
351
+ },
352
+ [agent.setModel],
353
+ );
354
+
355
+ const handleSetConfigOption = useCallback(
356
+ async (configId: string, value: string) => {
357
+ await agent.setConfigOption(configId, value);
358
+ },
359
+ [agent.setConfigOption],
360
+ );
361
+
362
+ // ============================================================
363
+ // UI State Actions
364
+ // ============================================================
365
+
366
+ const handleClearError = useCallback(() => {
367
+ agent.clearError();
368
+ }, [agent.clearError]);
369
+
370
+ const handleClearAgentUpdate = useCallback(() => {
371
+ setAgentUpdateNotification(null);
372
+ }, []);
373
+
374
+ const handleRestoredMessageConsumed = useCallback(() => {
375
+ setRestoredMessage(null);
376
+ }, []);
377
+
378
+ // ============================================================
379
+ // Return
380
+ // ============================================================
381
+
382
+ return {
383
+ handleSendMessage,
384
+ handleStopGeneration,
385
+ handleNewChat,
386
+ handleExportChat,
387
+ handleSwitchAgent,
388
+ handleRestartAgent,
389
+ handleSetMode,
390
+ handleSetModel,
391
+ handleSetConfigOption,
392
+ handleClearError,
393
+ handleClearAgentUpdate,
394
+ handleRestoredMessageConsumed,
395
+ restoredMessage,
396
+ agentUpdateNotification,
397
+ setAgentUpdateNotification,
398
+ autoExportIfEnabled,
399
+ };
400
+ }
@@ -0,0 +1,219 @@
1
+ import { useRef, useCallback, useEffect } from "react";
2
+ import { Notice, Platform } from "obsidian";
3
+ import { SessionHistoryModal } from "../ui/SessionHistoryModal";
4
+ import { getLogger } from "../utils/logger";
5
+ import { convertWslPathToWindows } from "../utils/platform";
6
+ import type AgentClientPlugin from "../plugin";
7
+ import type { UseAgentReturn } from "./useAgent";
8
+ import type { UseSessionHistoryReturn } from "./useSessionHistory";
9
+
10
+ /**
11
+ * Hook for managing the session history modal lifecycle.
12
+ *
13
+ * Encapsulates modal creation, props synchronization, and
14
+ * session operation callbacks (restore, fork, delete).
15
+ *
16
+ * @param plugin - Plugin instance for app access
17
+ * @param agent - Agent hook for clearMessages
18
+ * @param sessionHistory - Session history hook for operations
19
+ * @param vaultPath - Current working directory
20
+ * @param isSessionReady - Whether the session is ready
21
+ * @param debugMode - Whether debug mode is enabled
22
+ */
23
+ export function useHistoryModal(
24
+ plugin: AgentClientPlugin,
25
+ agent: UseAgentReturn,
26
+ sessionHistory: UseSessionHistoryReturn,
27
+ vaultPath: string,
28
+ isSessionReady: boolean,
29
+ debugMode: boolean,
30
+ onAgentCwdChange?: (cwd: string) => void,
31
+ ): {
32
+ handleOpenHistory: () => void;
33
+ } {
34
+ const logger = getLogger();
35
+ const historyModalRef = useRef<SessionHistoryModal | null>(null);
36
+
37
+ const handleRestoreSession = useCallback(
38
+ async (sessionId: string, cwd: string) => {
39
+ try {
40
+ logger.log(`[ChatPanel] Restoring session: ${sessionId}`);
41
+ agent.clearMessages();
42
+ await sessionHistory.restoreSession(sessionId, cwd);
43
+ onAgentCwdChange?.(
44
+ Platform.isWin ? convertWslPathToWindows(cwd) : cwd,
45
+ );
46
+ new Notice("[Agent Client] Session restored");
47
+ } catch (error) {
48
+ new Notice("[Agent Client] Failed to restore session");
49
+ logger.error("Session restore error:", error);
50
+ }
51
+ },
52
+ [
53
+ logger,
54
+ agent.clearMessages,
55
+ sessionHistory.restoreSession,
56
+ onAgentCwdChange,
57
+ ],
58
+ );
59
+
60
+ const handleForkSession = useCallback(
61
+ async (sessionId: string, cwd: string) => {
62
+ try {
63
+ logger.log(`[ChatPanel] Forking session: ${sessionId}`);
64
+ agent.clearMessages();
65
+ await sessionHistory.forkSession(sessionId, cwd);
66
+ onAgentCwdChange?.(
67
+ Platform.isWin ? convertWslPathToWindows(cwd) : cwd,
68
+ );
69
+ new Notice("[Agent Client] Session forked");
70
+ } catch (error) {
71
+ new Notice("[Agent Client] Failed to fork session");
72
+ logger.error("Session fork error:", error);
73
+ }
74
+ },
75
+ [
76
+ logger,
77
+ agent.clearMessages,
78
+ sessionHistory.forkSession,
79
+ onAgentCwdChange,
80
+ ],
81
+ );
82
+
83
+ const handleDeleteSession = useCallback(
84
+ async (sessionId: string) => {
85
+ try {
86
+ logger.log(`[ChatPanel] Deleting session: ${sessionId}`);
87
+ await sessionHistory.deleteSession(sessionId);
88
+ new Notice("[Agent Client] Session deleted");
89
+ } catch (error) {
90
+ new Notice("[Agent Client] Failed to delete session");
91
+ logger.error("Session delete error:", error);
92
+ }
93
+ },
94
+ [sessionHistory.deleteSession, logger],
95
+ );
96
+
97
+ const handleEditTitle = useCallback(
98
+ async (sessionId: string, newTitle: string, sessionCwd: string) => {
99
+ try {
100
+ await sessionHistory.updateSessionTitle(
101
+ sessionId,
102
+ newTitle,
103
+ sessionCwd,
104
+ );
105
+ new Notice("[Agent Client] Title updated");
106
+ } catch (error) {
107
+ new Notice("[Agent Client] Failed to update title");
108
+ logger.error("Title update error:", error);
109
+ }
110
+ },
111
+ [sessionHistory.updateSessionTitle, logger],
112
+ );
113
+
114
+ const handleLoadMore = useCallback(() => {
115
+ void sessionHistory.loadMoreSessions();
116
+ }, [sessionHistory.loadMoreSessions]);
117
+
118
+ const handleFetchSessions = useCallback(
119
+ (cwd?: string) => {
120
+ void sessionHistory.fetchSessions(cwd);
121
+ },
122
+ [sessionHistory.fetchSessions],
123
+ );
124
+
125
+ const handleOpenHistory = useCallback(() => {
126
+ // Create modal if it doesn't exist
127
+ if (!historyModalRef.current) {
128
+ historyModalRef.current = new SessionHistoryModal(plugin.app, {
129
+ sessions: sessionHistory.sessions,
130
+ loading: sessionHistory.loading,
131
+ error: sessionHistory.error,
132
+ hasMore: sessionHistory.hasMore,
133
+ currentCwd: vaultPath,
134
+ canList: sessionHistory.canList,
135
+ canRestore: sessionHistory.canRestore,
136
+ canFork: sessionHistory.canFork,
137
+ isUsingLocalSessions: sessionHistory.isUsingLocalSessions,
138
+ localSessionIds: sessionHistory.localSessionIds,
139
+ isAgentReady: isSessionReady,
140
+ debugMode: debugMode,
141
+ onRestoreSession: handleRestoreSession,
142
+ onForkSession: handleForkSession,
143
+ onDeleteSession: handleDeleteSession,
144
+ onEditTitle: handleEditTitle,
145
+ onLoadMore: handleLoadMore,
146
+ onFetchSessions: handleFetchSessions,
147
+ });
148
+ }
149
+ historyModalRef.current.open();
150
+ void sessionHistory.fetchSessions(vaultPath);
151
+ }, [
152
+ plugin.app,
153
+ sessionHistory.sessions,
154
+ sessionHistory.loading,
155
+ sessionHistory.error,
156
+ sessionHistory.hasMore,
157
+ sessionHistory.canList,
158
+ sessionHistory.canRestore,
159
+ sessionHistory.canFork,
160
+ sessionHistory.isUsingLocalSessions,
161
+ sessionHistory.localSessionIds,
162
+ sessionHistory.fetchSessions,
163
+ vaultPath,
164
+ isSessionReady,
165
+ debugMode,
166
+ handleRestoreSession,
167
+ handleForkSession,
168
+ handleDeleteSession,
169
+ handleEditTitle,
170
+ handleLoadMore,
171
+ handleFetchSessions,
172
+ ]);
173
+
174
+ // Update modal props when session history state changes
175
+ useEffect(() => {
176
+ if (historyModalRef.current) {
177
+ historyModalRef.current.updateProps({
178
+ sessions: sessionHistory.sessions,
179
+ loading: sessionHistory.loading,
180
+ error: sessionHistory.error,
181
+ hasMore: sessionHistory.hasMore,
182
+ currentCwd: vaultPath,
183
+ canList: sessionHistory.canList,
184
+ canRestore: sessionHistory.canRestore,
185
+ canFork: sessionHistory.canFork,
186
+ isUsingLocalSessions: sessionHistory.isUsingLocalSessions,
187
+ localSessionIds: sessionHistory.localSessionIds,
188
+ isAgentReady: isSessionReady,
189
+ debugMode: debugMode,
190
+ onRestoreSession: handleRestoreSession,
191
+ onForkSession: handleForkSession,
192
+ onDeleteSession: handleDeleteSession,
193
+ onEditTitle: handleEditTitle,
194
+ onLoadMore: handleLoadMore,
195
+ onFetchSessions: handleFetchSessions,
196
+ });
197
+ }
198
+ }, [
199
+ sessionHistory.sessions,
200
+ sessionHistory.loading,
201
+ sessionHistory.error,
202
+ sessionHistory.hasMore,
203
+ sessionHistory.canList,
204
+ sessionHistory.canRestore,
205
+ sessionHistory.canFork,
206
+ sessionHistory.isUsingLocalSessions,
207
+ vaultPath,
208
+ isSessionReady,
209
+ debugMode,
210
+ handleRestoreSession,
211
+ handleForkSession,
212
+ handleDeleteSession,
213
+ handleEditTitle,
214
+ handleLoadMore,
215
+ handleFetchSessions,
216
+ ]);
217
+
218
+ return { handleOpenHistory };
219
+ }