@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,285 @@
1
+ /**
2
+ * Settings Store Adapter
3
+ *
4
+ * Reactive settings store implementing ISettingAccess port.
5
+ * Manages plugin settings state with observer pattern for React integration
6
+ * via useSyncExternalStore, and handles persistence to Obsidian's data.json.
7
+ */
8
+
9
+ import type { AgentClientPluginSettings } from "../plugin";
10
+ import type AgentClientPlugin from "../plugin";
11
+ import type { ChatMessage } from "../types/chat";
12
+ import type { SavedSessionInfo } from "../types/session";
13
+ import { SessionStorage } from "./session-storage";
14
+
15
+ // ============================================================================
16
+ // Port Types (from settings-access.port.ts)
17
+ // ============================================================================
18
+
19
+ /**
20
+ * Interface for accessing and managing plugin settings.
21
+ *
22
+ * Provides reactive access to settings with subscription support
23
+ * for detecting changes (e.g., for React components using useSyncExternalStore).
24
+ *
25
+ * This port will be implemented by adapters that handle the actual
26
+ * storage mechanism (SettingsService, localStorage, etc.).
27
+ */
28
+ export interface ISettingsAccess {
29
+ /**
30
+ * Get the current settings snapshot.
31
+ *
32
+ * Used by React's useSyncExternalStore to read current state.
33
+ * Should return the settings object immediately without side effects.
34
+ *
35
+ * @returns Current plugin settings
36
+ */
37
+ getSnapshot(): AgentClientPluginSettings;
38
+
39
+ /**
40
+ * Update plugin settings.
41
+ *
42
+ * Merges the provided updates with existing settings and persists
43
+ * the changes. Notifies all subscribers after the update.
44
+ *
45
+ * @param updates - Partial settings object with properties to update
46
+ * @returns Promise that resolves when settings are saved
47
+ */
48
+ updateSettings(updates: Partial<AgentClientPluginSettings>): Promise<void>;
49
+
50
+ /**
51
+ * Subscribe to settings changes.
52
+ *
53
+ * The listener will be called whenever settings are updated.
54
+ * Used by React's useSyncExternalStore to detect changes and trigger re-renders.
55
+ *
56
+ * @param listener - Callback to invoke on settings changes
57
+ * @returns Unsubscribe function to remove the listener
58
+ */
59
+ subscribe(listener: () => void): () => void;
60
+
61
+ // ============================================================
62
+ // Session Storage Methods
63
+ // ============================================================
64
+
65
+ /**
66
+ * Save a session to local storage.
67
+ *
68
+ * Updates existing session if sessionId matches.
69
+ * Maintains max 50 sessions, removing oldest when exceeded.
70
+ *
71
+ * @param info - Session metadata to save
72
+ * @returns Promise that resolves when session is saved
73
+ */
74
+ saveSession(info: SavedSessionInfo): Promise<void>;
75
+
76
+ /**
77
+ * Get saved sessions, optionally filtered by agentId and/or cwd.
78
+ *
79
+ * Returns sessions sorted by updatedAt (newest first).
80
+ *
81
+ * @param agentId - Optional filter by agent ID
82
+ * @param cwd - Optional filter by working directory
83
+ * @returns Array of saved session metadata
84
+ */
85
+ getSavedSessions(agentId?: string, cwd?: string): SavedSessionInfo[];
86
+
87
+ /**
88
+ * Delete a saved session by sessionId.
89
+ *
90
+ * @param sessionId - ID of session to delete
91
+ * @returns Promise that resolves when session is deleted
92
+ */
93
+ deleteSession(sessionId: string): Promise<void>;
94
+
95
+ // ============================================================
96
+ // Session Message History Methods
97
+ // ============================================================
98
+
99
+ /**
100
+ * Save message history for a session.
101
+ *
102
+ * Saves the full ChatMessage[] to a separate file in sessions/ directory.
103
+ * Overwrites existing file if present.
104
+ *
105
+ * @param sessionId - Session ID
106
+ * @param agentId - Agent ID for validation
107
+ * @param messages - Chat messages to save
108
+ * @returns Promise that resolves when messages are saved
109
+ */
110
+ saveSessionMessages(
111
+ sessionId: string,
112
+ agentId: string,
113
+ messages: ChatMessage[],
114
+ ): Promise<void>;
115
+
116
+ /**
117
+ * Load message history for a session.
118
+ *
119
+ * Reads from sessions/{sessionId}.json file.
120
+ * Returns null if file doesn't exist.
121
+ *
122
+ * @param sessionId - Session ID
123
+ * @returns Promise that resolves with messages or null if not found
124
+ */
125
+ loadSessionMessages(sessionId: string): Promise<ChatMessage[] | null>;
126
+
127
+ /**
128
+ * Delete message history file for a session.
129
+ *
130
+ * Called when session is deleted from savedSessions.
131
+ * Silently succeeds if file doesn't exist.
132
+ *
133
+ * @param sessionId - Session ID
134
+ * @returns Promise that resolves when file is deleted
135
+ */
136
+ deleteSessionMessages(sessionId: string): Promise<void>;
137
+ }
138
+
139
+ /** Listener callback invoked when settings change */
140
+ type Listener = () => void;
141
+
142
+ /**
143
+ * Observable store for plugin settings implementing ISettingsAccess port.
144
+ *
145
+ * Manages plugin settings state and notifies subscribers of changes.
146
+ * Designed to work with React's useSyncExternalStore hook for
147
+ * automatic re-rendering when settings update.
148
+ *
149
+ * Pattern: Observer/Publisher-Subscriber
150
+ */
151
+ export class SettingsService implements ISettingsAccess {
152
+ /** Current settings state */
153
+ private state: AgentClientPluginSettings;
154
+
155
+ /** Set of registered listeners */
156
+ private listeners = new Set<Listener>();
157
+
158
+ /** Plugin instance for persistence */
159
+ private plugin: AgentClientPlugin;
160
+
161
+ /** Session storage delegate */
162
+ private sessionStorage: SessionStorage;
163
+
164
+ /**
165
+ * Create a new settings store.
166
+ *
167
+ * @param initial - Initial settings state
168
+ * @param plugin - Plugin instance for saving settings
169
+ */
170
+ constructor(initial: AgentClientPluginSettings, plugin: AgentClientPlugin) {
171
+ this.state = initial;
172
+ this.plugin = plugin;
173
+ this.sessionStorage = new SessionStorage(plugin, this);
174
+ }
175
+
176
+ /**
177
+ * Get current settings snapshot.
178
+ *
179
+ * Used by React's useSyncExternalStore to read current state.
180
+ *
181
+ * @returns Current plugin settings
182
+ */
183
+ getSnapshot = (): AgentClientPluginSettings => this.state;
184
+
185
+ /**
186
+ * Update plugin settings.
187
+ *
188
+ * Merges the provided updates with existing settings, notifies subscribers,
189
+ * and persists changes to disk.
190
+ *
191
+ * @param updates - Partial settings object with properties to update
192
+ * @returns Promise that resolves when settings are saved
193
+ */
194
+ async updateSettings(
195
+ updates: Partial<AgentClientPluginSettings>,
196
+ ): Promise<void> {
197
+ const next = { ...this.state, ...updates };
198
+ this.state = next;
199
+
200
+ // Sync with plugin.settings (required for saveSettings to persist correctly)
201
+ this.plugin.settings = next;
202
+
203
+ // Notify all subscribers
204
+ for (const listener of this.listeners) {
205
+ listener();
206
+ }
207
+
208
+ // Persist to disk
209
+ await this.plugin.saveSettings();
210
+ }
211
+
212
+ /**
213
+ * Subscribe to settings changes.
214
+ *
215
+ * The listener will be called whenever settings are updated via updateSettings().
216
+ * Used by React's useSyncExternalStore to detect changes.
217
+ *
218
+ * @param listener - Callback to invoke on settings changes
219
+ * @returns Unsubscribe function to remove the listener
220
+ */
221
+ subscribe = (listener: Listener): (() => void) => {
222
+ this.listeners.add(listener);
223
+ return () => this.listeners.delete(listener);
224
+ };
225
+
226
+ /**
227
+ * Set entire settings object (legacy method).
228
+ *
229
+ * For backward compatibility with existing code.
230
+ * Delegates to updateSettings() for async persistence.
231
+ *
232
+ * @param next - New settings object
233
+ */
234
+ set(next: AgentClientPluginSettings): void {
235
+ // Delegate to async updateSettings
236
+ // Note: Fire-and-forget - callers don't expect this to be async
237
+ void this.updateSettings(next);
238
+ }
239
+
240
+ // ============================================================
241
+ // Session Storage (delegated to SessionStorage)
242
+ // ============================================================
243
+
244
+ async saveSession(info: SavedSessionInfo): Promise<void> {
245
+ return this.sessionStorage.saveSession(info);
246
+ }
247
+
248
+ getSavedSessions(agentId?: string, cwd?: string): SavedSessionInfo[] {
249
+ return this.sessionStorage.getSavedSessions(agentId, cwd);
250
+ }
251
+
252
+ async deleteSession(sessionId: string): Promise<void> {
253
+ return this.sessionStorage.deleteSession(sessionId);
254
+ }
255
+
256
+ async saveSessionMessages(
257
+ sessionId: string,
258
+ agentId: string,
259
+ messages: ChatMessage[],
260
+ ): Promise<void> {
261
+ return this.sessionStorage.saveSessionMessages(
262
+ sessionId,
263
+ agentId,
264
+ messages,
265
+ );
266
+ }
267
+
268
+ async loadSessionMessages(
269
+ sessionId: string,
270
+ ): Promise<ChatMessage[] | null> {
271
+ return this.sessionStorage.loadSessionMessages(sessionId);
272
+ }
273
+
274
+ async deleteSessionMessages(sessionId: string): Promise<void> {
275
+ return this.sessionStorage.deleteSessionMessages(sessionId);
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Create a new settings store instance.
281
+ */
282
+ export const createSettingsService = (
283
+ initial: AgentClientPluginSettings,
284
+ plugin: AgentClientPlugin,
285
+ ) => new SettingsService(initial, plugin);
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Agent Update Checker
3
+ *
4
+ * Checks built-in agent ACP adapters for:
5
+ * 1. Package migration — deprecated packages that have been renamed
6
+ * 2. Version updates — newer versions available on npm
7
+ *
8
+ * Pure functions (non-React). Uses Obsidian's requestUrl for network access.
9
+ */
10
+
11
+ import { requestUrl } from "obsidian";
12
+ import * as semver from "semver";
13
+ import type { OverlayVariant } from "../types/errors";
14
+
15
+ // ============================================================================
16
+ // Types
17
+ // ============================================================================
18
+
19
+ /**
20
+ * Agent update notification to display in the UI.
21
+ * Compatible with ErrorInfo shape (title/message/suggestion).
22
+ */
23
+ export interface AgentUpdateNotification {
24
+ /** Visual variant for the overlay */
25
+ variant: OverlayVariant;
26
+ /** Short notification title */
27
+ title: string;
28
+ /** Detailed notification message */
29
+ message: string;
30
+ /** Actionable suggestion (e.g., npm command) */
31
+ suggestion?: string;
32
+ /** Optional external link rendered as an actionable anchor (e.g. docs). */
33
+ link?: { text: string; url: string };
34
+ }
35
+
36
+ // ============================================================================
37
+ // Known Packages
38
+ // ============================================================================
39
+
40
+ /**
41
+ * Maps agentInfo.name → npm package name.
42
+ * Agents may report their name with or without the npm scope prefix,
43
+ * so we handle both forms.
44
+ */
45
+ const KNOWN_AGENT_PACKAGES: Readonly<Record<string, string>> = {
46
+ "@agentclientprotocol/claude-agent-acp":
47
+ "@agentclientprotocol/claude-agent-acp",
48
+ "codex-acp": "@zed-industries/codex-acp",
49
+ };
50
+
51
+ /**
52
+ * Deprecated agentInfo.name → replacement npm package name.
53
+ * Used to detect users still running old/renamed packages.
54
+ */
55
+ const DEPRECATED_PACKAGES: Readonly<Record<string, string>> = {
56
+ "@zed-industries/claude-code-acp": "@agentclientprotocol/claude-agent-acp",
57
+ "@zed-industries/claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
58
+ };
59
+
60
+ // ============================================================================
61
+ // Public API
62
+ // ============================================================================
63
+
64
+ /**
65
+ * Check if the agent needs a package migration or version update.
66
+ *
67
+ * Priority: migration notification > version update notification.
68
+ * - Migration is checked locally (no network) based on agentInfo.name.
69
+ * - Version update queries the npm registry.
70
+ *
71
+ * @returns AgentUpdateNotification if action needed, null otherwise.
72
+ */
73
+ export async function checkAgentUpdate(agentInfo: {
74
+ name: string;
75
+ version?: string;
76
+ }): Promise<AgentUpdateNotification | null> {
77
+ // 1. Check for deprecated package (migration takes priority)
78
+ const replacement = DEPRECATED_PACKAGES[agentInfo.name];
79
+ if (replacement) {
80
+ return {
81
+ variant: "info",
82
+ title: "Package Migration Required",
83
+ message: `"${agentInfo.name}" has been renamed to "${replacement}".\nRun the following in your terminal:`,
84
+ suggestion: `npm uninstall -g ${agentInfo.name} && npm install -g ${replacement}`,
85
+ };
86
+ }
87
+
88
+ // 2. Check for version update (known packages only)
89
+ const npmPackage = KNOWN_AGENT_PACKAGES[agentInfo.name];
90
+ if (!npmPackage || !agentInfo.version) {
91
+ return null;
92
+ }
93
+
94
+ try {
95
+ const latestVersion = await fetchLatestVersion(npmPackage);
96
+ if (
97
+ latestVersion &&
98
+ semver.valid(agentInfo.version) &&
99
+ semver.gt(latestVersion, agentInfo.version)
100
+ ) {
101
+ return {
102
+ variant: "info",
103
+ title: "Agent Update Available",
104
+ message: `${npmPackage}: ${agentInfo.version} → ${latestVersion}.\nRun the following in your terminal:`,
105
+ suggestion: `npm install -g ${npmPackage}@latest`,
106
+ };
107
+ }
108
+ } catch {
109
+ // Silently ignore network errors — update check is best-effort
110
+ }
111
+
112
+ return null;
113
+ }
114
+
115
+ // ============================================================================
116
+ // Internal
117
+ // ============================================================================
118
+
119
+ /**
120
+ * Fetch the latest version of an npm package from the registry.
121
+ */
122
+ async function fetchLatestVersion(packageName: string): Promise<string | null> {
123
+ const response = await requestUrl({
124
+ url: `https://registry.npmjs.org/${packageName}/latest`,
125
+ });
126
+ const data = response.json as { version?: string };
127
+ return data.version ? (semver.clean(data.version) ?? null) : null;
128
+ }