@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,345 @@
1
+ /**
2
+ * Registry for managing all chat view containers.
3
+ *
4
+ * Provides unified access to views for:
5
+ * - Focus tracking (replacing _lastActiveChatViewId + floating tracking)
6
+ * - Broadcast commands (extending to all view types)
7
+ * - Multi-view operations (focusNext, toAll, etc.)
8
+ *
9
+ * Design notes:
10
+ * - Views register themselves on mount, unregister on close
11
+ * - Focus is tracked via focusedViewId
12
+ * - Registry does not own view lifecycle, only tracks references
13
+ * - clear() is called during plugin unload for cleanup
14
+ * - focusNext/Previous order is based on registration order, not workspace leaf order
15
+ * (this is acceptable as users don't have strong expectations about the order)
16
+ */
17
+
18
+ import type { ChatInputState } from "../types/chat";
19
+ import { getLogger } from "../utils/logger";
20
+
21
+ // ============================================================================
22
+ // Port Types (from chat-view-container.port.ts)
23
+ // ============================================================================
24
+
25
+ /**
26
+ * Type of chat view container.
27
+ * Used for filtering and type-specific behavior.
28
+ */
29
+ export type ChatViewType = "sidebar" | "floating";
30
+
31
+ /**
32
+ * Interface that all chat view containers must implement.
33
+ * Enables the plugin to manage views uniformly regardless of their implementation.
34
+ */
35
+ export interface IChatViewContainer {
36
+ // ============================================================
37
+ // Identification
38
+ // ============================================================
39
+
40
+ /** Unique identifier for this view instance */
41
+ readonly viewId: string;
42
+
43
+ /** Type of this view (sidebar, floating, etc.) */
44
+ readonly viewType: ChatViewType;
45
+
46
+ /** Human-readable display name for this view (e.g. active agent label). */
47
+ getDisplayName(): string;
48
+
49
+ // ============================================================
50
+ // Lifecycle
51
+ // ============================================================
52
+
53
+ /**
54
+ * Called when this view becomes the active/focused view.
55
+ * Triggered by ChatViewRegistry.setFocused().
56
+ */
57
+ onActivate(): void;
58
+
59
+ /**
60
+ * Called when this view loses active/focused status.
61
+ * Triggered by ChatViewRegistry.setFocused() or unregister().
62
+ */
63
+ onDeactivate(): void;
64
+
65
+ // ============================================================
66
+ // Focus Management
67
+ // ============================================================
68
+
69
+ /**
70
+ * Programmatically focus this view's input.
71
+ * Should focus the chat input textarea.
72
+ * For floating views, this also expands the window if collapsed.
73
+ */
74
+ focus(): void;
75
+
76
+ /**
77
+ * Check if this view currently has focus.
78
+ * Returns true if any element within this view's container is focused.
79
+ */
80
+ hasFocus(): boolean;
81
+
82
+ /**
83
+ * Expand the view if it's in a collapsed state.
84
+ * For sidebar views, this is a no-op.
85
+ * For floating views, this expands the window.
86
+ *
87
+ * Note: This method is provided for explicit expand operations (e.g., from UI).
88
+ * When focus() is called, it internally handles expansion before focusing.
89
+ * ChatViewRegistry uses focus() which implicitly expands, so expand() is not
90
+ * directly called by the registry.
91
+ */
92
+ expand(): void;
93
+
94
+ /**
95
+ * Collapse the view if it's in an expanded state.
96
+ * For sidebar views, this is a no-op.
97
+ * For floating views, this hides the window without destroying the instance.
98
+ */
99
+ collapse(): void;
100
+
101
+ // ============================================================
102
+ // Broadcast Commands
103
+ // ============================================================
104
+
105
+ /**
106
+ * Get current input state (text + images) for broadcast.
107
+ * Returns null if input state is not available.
108
+ */
109
+ getInputState(): ChatInputState | null;
110
+
111
+ /**
112
+ * Set input state (text + images) from broadcast.
113
+ * Used to copy prompt from one view to another.
114
+ */
115
+ setInputState(state: ChatInputState): void;
116
+
117
+ /**
118
+ * Check if this view is ready to send a message.
119
+ * Returns true if:
120
+ * - Session is ready
121
+ * - Not currently sending
122
+ * - Not loading session history
123
+ * - Has content (text or images)
124
+ */
125
+ canSend(): boolean;
126
+
127
+ /**
128
+ * Trigger send message with full support for images.
129
+ * @returns Promise<boolean> - true if message was sent, false otherwise
130
+ */
131
+ sendMessage(): Promise<boolean>;
132
+
133
+ /**
134
+ * Cancel current operation.
135
+ * Stops ongoing message generation.
136
+ */
137
+ cancelOperation(): Promise<void>;
138
+
139
+ // ============================================================
140
+ // Container Access
141
+ // ============================================================
142
+
143
+ /**
144
+ * Get the DOM container element for this view.
145
+ * Used for focus detection and DOM queries.
146
+ */
147
+ getContainerEl(): HTMLElement;
148
+ }
149
+
150
+ export class ChatViewRegistry {
151
+ private views = new Map<string, IChatViewContainer>();
152
+ private focusedViewId: string | null = null;
153
+ private logger = getLogger();
154
+
155
+ // ============================================================
156
+ // Registration
157
+ // ============================================================
158
+
159
+ /**
160
+ * Register a view container.
161
+ * The first registered view automatically becomes focused.
162
+ */
163
+ register(view: IChatViewContainer): void {
164
+ this.logger.log(
165
+ `[ChatViewRegistry] Registering view: ${view.viewId} (${view.viewType})`,
166
+ );
167
+ this.views.set(view.viewId, view);
168
+
169
+ // First view becomes focused by default
170
+ if (this.views.size === 1) {
171
+ this.setFocused(view.viewId);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Unregister a view container.
177
+ * If the focused view is unregistered, focus moves to another view.
178
+ */
179
+ unregister(viewId: string): void {
180
+ this.logger.log(`[ChatViewRegistry] Unregistering view: ${viewId}`);
181
+ const view = this.views.get(viewId);
182
+ if (view) {
183
+ view.onDeactivate();
184
+ }
185
+ this.views.delete(viewId);
186
+
187
+ // Move focus if this was the focused view
188
+ if (this.focusedViewId === viewId) {
189
+ const remaining = Array.from(this.views.keys());
190
+ this.focusedViewId = remaining.length > 0 ? remaining[0] : null;
191
+ if (this.focusedViewId) {
192
+ this.views.get(this.focusedViewId)?.onActivate();
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Clear all views from the registry.
199
+ * Called during plugin unload to clean up resources.
200
+ * Note: This does NOT call unmount() on views - that should be done separately.
201
+ */
202
+ clear(): void {
203
+ this.logger.log("[ChatViewRegistry] Clearing all views");
204
+ for (const view of this.views.values()) {
205
+ view.onDeactivate();
206
+ }
207
+ this.views.clear();
208
+ this.focusedViewId = null;
209
+ }
210
+
211
+ // ============================================================
212
+ // Focus Management
213
+ // ============================================================
214
+
215
+ /**
216
+ * Get the currently focused view.
217
+ */
218
+ getFocused(): IChatViewContainer | null {
219
+ return this.focusedViewId
220
+ ? (this.views.get(this.focusedViewId) ?? null)
221
+ : null;
222
+ }
223
+
224
+ /**
225
+ * Get the focused view ID.
226
+ */
227
+ getFocusedId(): string | null {
228
+ return this.focusedViewId;
229
+ }
230
+
231
+ /**
232
+ * Set a view as focused.
233
+ */
234
+ setFocused(viewId: string): void {
235
+ if (this.focusedViewId === viewId) return;
236
+ if (!this.views.has(viewId)) return;
237
+
238
+ // Deactivate previous
239
+ if (this.focusedViewId) {
240
+ this.views.get(this.focusedViewId)?.onDeactivate();
241
+ }
242
+
243
+ // Activate new
244
+ this.focusedViewId = viewId;
245
+ this.views.get(viewId)?.onActivate();
246
+ this.logger.log(`[ChatViewRegistry] Focus changed to: ${viewId}`);
247
+ }
248
+
249
+ /**
250
+ * Focus the next view in the list (cyclic).
251
+ * Order is based on registration order (Map insertion order).
252
+ */
253
+ focusNext(): void {
254
+ const ids = Array.from(this.views.keys());
255
+ if (ids.length === 0) return;
256
+
257
+ const currentIndex = this.focusedViewId
258
+ ? ids.indexOf(this.focusedViewId)
259
+ : -1;
260
+ const nextIndex = (currentIndex + 1) % ids.length;
261
+ this.setFocused(ids[nextIndex]);
262
+ this.views.get(ids[nextIndex])?.focus();
263
+ }
264
+
265
+ /**
266
+ * Focus the previous view in the list (cyclic).
267
+ * Order is based on registration order (Map insertion order).
268
+ */
269
+ focusPrevious(): void {
270
+ const ids = Array.from(this.views.keys());
271
+ if (ids.length === 0) return;
272
+
273
+ const currentIndex = this.focusedViewId
274
+ ? ids.indexOf(this.focusedViewId)
275
+ : 0;
276
+ const prevIndex = (currentIndex - 1 + ids.length) % ids.length;
277
+ this.setFocused(ids[prevIndex]);
278
+ this.views.get(ids[prevIndex])?.focus();
279
+ }
280
+
281
+ // ============================================================
282
+ // Broadcast Operations
283
+ // ============================================================
284
+
285
+ /**
286
+ * Execute action on the focused view only.
287
+ */
288
+ toFocused<T>(action: (view: IChatViewContainer) => T): T | null {
289
+ const focused = this.getFocused();
290
+ return focused ? action(focused) : null;
291
+ }
292
+
293
+ /**
294
+ * Execute action on all views.
295
+ */
296
+ toAll(action: (view: IChatViewContainer) => void): void {
297
+ this.views.forEach(action);
298
+ }
299
+
300
+ /**
301
+ * Execute action on views of a specific type.
302
+ */
303
+ toType(
304
+ type: ChatViewType,
305
+ action: (view: IChatViewContainer) => void,
306
+ ): void {
307
+ this.views.forEach((view) => {
308
+ if (view.viewType === type) action(view);
309
+ });
310
+ }
311
+
312
+ // ============================================================
313
+ // Query
314
+ // ============================================================
315
+
316
+ /**
317
+ * Get all registered views.
318
+ */
319
+ getAll(): IChatViewContainer[] {
320
+ return Array.from(this.views.values());
321
+ }
322
+
323
+ /**
324
+ * Get views of a specific type.
325
+ */
326
+ getByType(type: ChatViewType): IChatViewContainer[] {
327
+ return Array.from(this.views.values()).filter(
328
+ (v) => v.viewType === type,
329
+ );
330
+ }
331
+
332
+ /**
333
+ * Get a view by ID.
334
+ */
335
+ get(viewId: string): IChatViewContainer | null {
336
+ return this.views.get(viewId) ?? null;
337
+ }
338
+
339
+ /**
340
+ * Get count of registered views.
341
+ */
342
+ get size(): number {
343
+ return this.views.size;
344
+ }
345
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Domain Models for Agent Configuration
3
+ *
4
+ * These types represent agent settings and configuration,
5
+ * independent of the plugin infrastructure. They define
6
+ * the core concepts of agent identity, capabilities, and
7
+ * connection parameters.
8
+ */
9
+
10
+ // ============================================================================
11
+ // Environment Configuration
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Environment variable for agent process.
16
+ *
17
+ * Used to pass configuration and credentials to agent processes
18
+ * via environment variables (e.g., API keys, paths, feature flags).
19
+ */
20
+ export interface AgentEnvVar {
21
+ /** Environment variable name (e.g., "ANTHROPIC_API_KEY") */
22
+ key: string;
23
+
24
+ /** Environment variable value */
25
+ value: string;
26
+ }
27
+
28
+ // ============================================================================
29
+ // Agent Configuration
30
+ // ============================================================================
31
+
32
+ /**
33
+ * Base configuration shared by all agent types.
34
+ *
35
+ * Defines the common properties needed to launch and communicate
36
+ * with any ACP-compatible agent, regardless of the specific
37
+ * implementation (Claude Code, Gemini CLI, custom agents, etc.).
38
+ */
39
+ export interface BaseAgentSettings {
40
+ /** Unique identifier for this agent (e.g., "claude", "gemini", "custom-1") */
41
+ id: string;
42
+
43
+ /** Human-readable display name shown in UI */
44
+ displayName: string;
45
+
46
+ /** Command to execute (full path to executable or command name) */
47
+ command: string;
48
+
49
+ /** Command-line arguments passed to the agent */
50
+ args: string[];
51
+
52
+ /** Environment variables for the agent process */
53
+ env: AgentEnvVar[];
54
+ }
55
+
56
+ /**
57
+ * Configuration for Gemini CLI agent.
58
+ *
59
+ * Extends base settings with Gemini-specific requirements.
60
+ */
61
+ export interface GeminiAgentSettings extends BaseAgentSettings {
62
+ /** Gemini API key (GEMINI_API_KEY) */
63
+ apiKey: string;
64
+ }
65
+
66
+ /**
67
+ * Configuration for Claude Code agent.
68
+ *
69
+ * Extends base settings with Claude-specific requirements.
70
+ */
71
+ export interface ClaudeAgentSettings extends BaseAgentSettings {
72
+ /** Anthropic API key for Claude (ANTHROPIC_API_KEY) */
73
+ apiKey: string;
74
+ }
75
+
76
+ /**
77
+ * Configuration for Codex CLI agent.
78
+ *
79
+ * Extends base settings with Codex-specific requirements.
80
+ */
81
+ export interface CodexAgentSettings extends BaseAgentSettings {
82
+ /** OpenAI API key for Codex (OPENAI_API_KEY) */
83
+ apiKey: string;
84
+ }
85
+
86
+ /**
87
+ * Configuration for custom ACP-compatible agents.
88
+ *
89
+ * Uses only the base settings, allowing users to configure
90
+ * any agent that implements the Agent Client Protocol.
91
+ */
92
+ export type CustomAgentSettings = BaseAgentSettings;