@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,58 @@
1
+ name: Deploy Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ paths:
7
+ - "docs/**"
8
+ - ".github/workflows/docs.yml"
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+ pages: write
14
+ id-token: write
15
+
16
+ concurrency:
17
+ group: pages
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - name: Checkout
25
+ uses: actions/checkout@v4
26
+ with:
27
+ fetch-depth: 0
28
+
29
+ - name: Setup Node
30
+ uses: actions/setup-node@v4
31
+ with:
32
+ node-version: 22
33
+ cache: npm
34
+
35
+ - name: Setup Pages
36
+ uses: actions/configure-pages@v4
37
+
38
+ - name: Install dependencies
39
+ run: npm ci
40
+
41
+ - name: Build with VitePress
42
+ run: npm run docs:build
43
+
44
+ - name: Upload artifact
45
+ uses: actions/upload-pages-artifact@v3
46
+ with:
47
+ path: docs/.vitepress/dist
48
+
49
+ deploy:
50
+ environment:
51
+ name: github-pages
52
+ url: ${{ steps.deployment.outputs.page_url }}
53
+ needs: build
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - name: Deploy to GitHub Pages
57
+ id: deployment
58
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,59 @@
1
+ # Relay GitHub events to OpenClaw via webhook
2
+ # OpenClaw translates the event into Japanese and posts it to Discord as an embed
3
+ #
4
+ # Required secrets:
5
+ # OPENCLAW_WEBHOOK_URL - OpenClaw hooks endpoint URL
6
+ # OPENCLAW_HOOKS_TOKEN - Bearer token for OpenClaw hooks authentication
7
+ # OPENCLAW_DISCORD_CHANNEL - Discord channel ID for delivery
8
+
9
+ name: Relay to OpenClaw
10
+
11
+ on:
12
+ issues:
13
+ types: [opened, reopened]
14
+ issue_comment:
15
+ types: [created]
16
+ pull_request_target:
17
+ types: [opened]
18
+ pull_request_review_comment:
19
+ types: [created]
20
+ discussion:
21
+ types: [created]
22
+ discussion_comment:
23
+ types: [created]
24
+
25
+ jobs:
26
+ relay:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - name: Forward event to OpenClaw
30
+ env:
31
+ GH_EVENT: ${{ toJSON(github.event) }}
32
+ OPENCLAW_WEBHOOK_URL: ${{ secrets.OPENCLAW_WEBHOOK_URL }}
33
+ OPENCLAW_HOOKS_TOKEN: ${{ secrets.OPENCLAW_HOOKS_TOKEN }}
34
+ OPENCLAW_DISCORD_CHANNEL: ${{ secrets.OPENCLAW_DISCORD_CHANNEL }}
35
+ run: |
36
+ EVENT_NAME="${{ github.event_name }}"
37
+ REPO="${{ github.repository }}"
38
+ NUMBER=$(echo "$GH_EVENT" | jq -r '.issue.number // .pull_request.number // .discussion.number // ""')
39
+ TITLE=$(echo "$GH_EVENT" | jq -r '.issue.title // .pull_request.title // .discussion.title // ""')
40
+ BODY=$(echo "$GH_EVENT" | jq -r '.comment.body // .issue.body // .pull_request.body // .discussion.body // ""' | head -c 4000)
41
+ URL=$(echo "$GH_EVENT" | jq -r '.comment.html_url // .issue.html_url // .pull_request.html_url // .discussion.html_url // ""')
42
+ USER=$(echo "$GH_EVENT" | jq -r '.sender.login // ""')
43
+
44
+ jq -n \
45
+ --arg event "$EVENT_NAME" \
46
+ --arg repo "$REPO" \
47
+ --arg number "$NUMBER" \
48
+ --arg title "$TITLE" \
49
+ --arg user "$USER" \
50
+ --arg url "$URL" \
51
+ --arg body "$BODY" \
52
+ --arg ch "discord" \
53
+ --arg to "$OPENCLAW_DISCORD_CHANNEL" \
54
+ '{message: ("event: " + $event + "\nrepo: " + $repo + "\nnumber: #" + $number + "\ntitle: " + $title + "\nauthor: " + $user + "\nurl: " + $url + "\n\nbody:\n" + $body), deliver: false, channel: $ch, to: $to, name: "GitHub", agentId: "github-hook"}' | \
55
+ curl -sf -X POST \
56
+ "${OPENCLAW_WEBHOOK_URL}/hooks/agent" \
57
+ -H "Authorization: Bearer ${OPENCLAW_HOOKS_TOKEN}" \
58
+ -H "Content-Type: application/json" \
59
+ -d @-
@@ -0,0 +1,45 @@
1
+ name: Release Obsidian plugin
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "*"
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ id-token: write
14
+ attestations: write
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Use Node.js
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: "22.x"
22
+
23
+ - name: Install dependencies
24
+ run: npm ci
25
+
26
+ - name: Build plugin
27
+ run: npm run build
28
+
29
+ - name: Attest build provenance
30
+ uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2
31
+ with:
32
+ subject-path: |
33
+ main.js
34
+ styles.css
35
+
36
+ - name: Create release
37
+ env:
38
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39
+ run: |
40
+ tag="${GITHUB_REF#refs/tags/}"
41
+
42
+ gh release create "$tag" \
43
+ --title="$tag" \
44
+ --draft \
45
+ main.js manifest.json styles.css
@@ -0,0 +1,10 @@
1
+ node_modules/
2
+ .git/
3
+ .github/
4
+ .serena/
5
+ .claude/
6
+ main.js
7
+ *.json
8
+ *.md
9
+ data.json
10
+ docs/
package/.prettierrc ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "useTabs": true,
3
+ "tabWidth": 4,
4
+ "semi": true,
5
+ "singleQuote": false,
6
+ "quoteProps": "as-needed",
7
+ "trailingComma": "all",
8
+ "bracketSpacing": true,
9
+ "arrowParens": "always",
10
+ "endOfLine": "lf",
11
+ "printWidth": 80,
12
+ "proseWrap": "preserve"
13
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "recommendations": [
3
+ "esbenp.prettier-vscode",
4
+ "dbaeumer.vscode-eslint",
5
+ "editorconfig.editorconfig"
6
+ ]
7
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "editor.formatOnSave": true,
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
4
+ "editor.codeActionsOnSave": {
5
+ "source.fixAll.eslint": "explicit"
6
+ },
7
+ "editor.tabSize": 4,
8
+ "editor.insertSpaces": false,
9
+ "editor.detectIndentation": false,
10
+ "files.eol": "\n",
11
+ "files.insertFinalNewline": true,
12
+ "files.trimTrailingWhitespace": true,
13
+ "[typescript]": {
14
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
15
+ },
16
+ "[typescriptreact]": {
17
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
18
+ },
19
+ "[javascript]": {
20
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
21
+ },
22
+ "[javascriptreact]": {
23
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
24
+ },
25
+ "[css]": {
26
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
27
+ },
28
+ "eslint.validate": [
29
+ "javascript",
30
+ "javascriptreact",
31
+ "typescript",
32
+ "typescriptreact"
33
+ ],
34
+ "eslint.enable": true,
35
+ "prettier.requireConfig": true,
36
+ "prettier.useEditorConfig": true
37
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "tab_size": 4,
3
+ "hard_tabs": true,
4
+ "format_on_save": "on",
5
+ "remove_trailing_whitespace_on_save": true,
6
+ "ensure_final_newline_on_save": true,
7
+ "languages": {
8
+ "TypeScript": {
9
+ "tab_size": 4,
10
+ "hard_tabs": true,
11
+ "formatter": "prettier",
12
+ "code_actions_on_format": {
13
+ "source.fixAll.eslint": false
14
+ },
15
+ "format_on_save": "on"
16
+ },
17
+ "TSX": {
18
+ "tab_size": 4,
19
+ "hard_tabs": true,
20
+ "formatter": "prettier",
21
+ "code_actions_on_format": {
22
+ "source.fixAll.eslint": false
23
+ },
24
+ "format_on_save": "on"
25
+ },
26
+ "JavaScript": {
27
+ "tab_size": 4,
28
+ "hard_tabs": true,
29
+ "formatter": "prettier",
30
+ "code_actions_on_format": {
31
+ "source.fixAll.eslint": false
32
+ },
33
+ "format_on_save": "on"
34
+ },
35
+ "CSS": {
36
+ "tab_size": 4,
37
+ "hard_tabs": true,
38
+ "formatter": "prettier",
39
+ "format_on_save": "on"
40
+ }
41
+ }
42
+ }
package/AGENTS.md ADDED
@@ -0,0 +1,330 @@
1
+ # Agent Client Plugin - LLM Developer Guide
2
+
3
+ ## Overview
4
+ Obsidian plugin for AI agent interaction (Claude Code, Codex, Gemini CLI, custom agents) via ACP.
5
+
6
+ **Tech**: React 19, TypeScript, Obsidian API, Agent Client Protocol (ACP)
7
+
8
+ ## Architecture
9
+
10
+ ```
11
+ src/
12
+ ├── types/ # Type definitions (no logic, no dependencies)
13
+ │ ├── chat.ts # ChatMessage, MessageContent, PromptContent, AttachedFile, ActivePermission
14
+ │ ├── session.ts # ChatSession, SessionUpdate (12-type union), SessionInfo, Capabilities
15
+ │ ├── agent.ts # AgentConfig, agent settings (Claude/Codex/Gemini/Custom)
16
+ │ └── errors.ts # AcpError, ProcessError, ErrorInfo
17
+ ├── acp/ # ACP protocol (SDK dependency confined here)
18
+ │ ├── acp-client.ts # Process lifecycle, UI-facing API (AcpClient class)
19
+ │ ├── acp-handler.ts # SDK event handler + sessionId filter + listener broadcast
20
+ │ ├── type-converter.ts # ACP SDK ↔ internal type conversion
21
+ │ ├── permission-handler.ts # Permission queue, auto-approve, Promise resolution
22
+ │ └── terminal-handler.ts # Terminal process create/output/kill
23
+ ├── services/ # Business logic (non-React, no React imports)
24
+ │ ├── vault-service.ts # Vault access + fuzzy search + CM6 selection tracking
25
+ │ ├── settings-service.ts # Reactive settings store (observer pattern only)
26
+ │ ├── session-storage.ts # Session metadata + message file I/O (sessions/*.json)
27
+ │ ├── settings-normalizer.ts # Settings validation helpers (str, bool, num, enumVal, etc.)
28
+ │ ├── session-helpers.ts # Agent config building, API key injection (pure functions)
29
+ │ ├── session-state.ts # Session state updates (legacy mode/model, config restore)
30
+ │ ├── message-state.ts # Message array transforms (upsert, merge, streaming apply)
31
+ │ ├── message-sender.ts # Prompt preparation + sending (pure functions)
32
+ │ ├── chat-exporter.ts # Markdown export with frontmatter
33
+ │ ├── view-registry.ts # Multi-view management, focus, broadcast
34
+ │ └── update-checker.ts # Agent/plugin version checking
35
+ ├── hooks/ # React custom hooks (state + logic)
36
+ │ ├── useAgent.ts # Facade: composes useAgentSession + useAgentMessages
37
+ │ ├── useAgentSession.ts # Session lifecycle, config options, optimistic updates
38
+ │ ├── useAgentMessages.ts # Message state, streaming (RAF batch), permissions
39
+ │ ├── useSuggestions.ts # @[[note]] mentions + /command suggestions (unified)
40
+ │ ├── useSessionHistory.ts # Session list/load/resume/fork
41
+ │ ├── useChatActions.ts # Business callbacks (send, newChat, export, restart, etc.)
42
+ │ ├── useHistoryModal.ts # Session history modal lifecycle
43
+ │ └── useSettings.ts # Settings subscription (useSyncExternalStore)
44
+ ├── ui/ # React components
45
+ │ ├── ChatContext.ts # React Context (plugin, acpClient, vaultService, settingsService)
46
+ │ ├── ChatPanel.tsx # Orchestrator: calls hooks, workspace events, rendering
47
+ │ ├── ChatView.tsx # Sidebar view (ItemView wrapper)
48
+ │ ├── FloatingChatView.tsx # Floating window (position/drag/resize)
49
+ │ ├── ChatHeader.tsx # Header (sidebar + floating variants)
50
+ │ ├── MessageList.tsx # Virtualized message list (@tanstack/react-virtual)
51
+ │ ├── MessageBubble.tsx # Single message rendering (content dispatch, copy button)
52
+ │ ├── ToolCallBlock.tsx # Tool call + diff display (word-level highlighting)
53
+ │ ├── TerminalBlock.tsx # Terminal output polling
54
+ │ ├── InputArea.tsx # Textarea, attachments, mentions, history
55
+ │ ├── InputToolbar.tsx # Config/mode/model selectors, usage, send button
56
+ │ ├── SuggestionPopup.tsx # Mention/command dropdown
57
+ │ ├── PermissionBanner.tsx # Permission request buttons
58
+ │ ├── ErrorBanner.tsx # Error/notification overlay
59
+ │ ├── SessionHistoryModal.tsx # Session history modal (list + confirm delete)
60
+ │ ├── FloatingButton.tsx # Draggable launch button
61
+ │ ├── SettingsTab.ts # Plugin settings UI
62
+ │ ├── view-host.ts # IChatViewHost interface
63
+ │ └── shared/
64
+ │ ├── IconButton.tsx # Icon button + Lucide icon wrapper
65
+ │ ├── MarkdownRenderer.tsx # Obsidian markdown rendering
66
+ │ └── AttachmentStrip.tsx # Attachment preview strip
67
+ ├── utils/ # Shared utilities (pure functions)
68
+ │ ├── platform.ts # Shell, WSL, Windows env, command building
69
+ │ ├── paths.ts # Path resolution, file:// URI
70
+ │ ├── error-utils.ts # ACP error conversion
71
+ │ ├── mention-parser.ts # @[[note]] detection/extraction
72
+ │ └── logger.ts # Debug-mode logger
73
+ ├── plugin.ts # Obsidian plugin lifecycle, settings persistence
74
+ └── main.ts # Entry point
75
+ ```
76
+
77
+ ## Data Flow
78
+
79
+ ### ACP Event Flow (single path)
80
+ ```
81
+ Agent Process → ACP SDK → AcpHandler (sessionId filter) → listeners broadcast
82
+ → useAgentSession (session-level: commands, mode, config, usage, error)
83
+ → useAgentMessages (message-level: text chunks, tool calls, plan)
84
+ → useAgent (facade, 1 onSessionUpdate subscription)
85
+ ```
86
+
87
+ All events flow through a single `onSessionUpdate` channel. No special paths for permissions or errors.
88
+
89
+ ### Permission Flow
90
+ ```
91
+ Agent requestPermission → PermissionManager.request() → onSessionUpdate (tool_call)
92
+ User clicks approve/reject → PermissionManager.respond() → onSessionUpdate (tool_call_update)
93
+ ```
94
+
95
+ ## Key Components
96
+
97
+ ### ChatPanel (`ui/ChatPanel.tsx`)
98
+ Central orchestrator component.
99
+ - **Hook Composition**: Calls useAgent, useSuggestions, useSessionHistory, useChatActions, useHistoryModal, useSettings
100
+ - **Workspace Events**: Handles hotkeys via ref pattern (stable event registration)
101
+ - **Callback Registration**: IChatViewContainer callbacks via refs
102
+ - **Rendering**: Renders ChatHeader, MessageList, InputArea directly
103
+
104
+ ChatPanel does NOT route session updates — that's handled internally by useAgent.
105
+
106
+ ### ChatView / FloatingChatView (`ui/ChatView.tsx`, `ui/FloatingChatView.tsx`)
107
+ Thin wrappers that:
108
+ - Create services (AcpClient, VaultService) in lifecycle methods
109
+ - Provide ChatContext (plugin, acpClient, vaultService, settingsService)
110
+ - Render `<ChatPanel variant="sidebar" | "floating" />`
111
+ - Implement IChatViewContainer for broadcast commands
112
+
113
+ FloatingChatView uses `onRegisterExpanded` callback (not CustomEvent) for expand/collapse.
114
+
115
+ ### Hooks (`hooks/`)
116
+
117
+ **useAgent** (facade): Comp훈oses useAgentSession + useAgentMessages
118
+ - Single `onSessionUpdate` subscription
119
+ - Unified `handleSessionUpdate` dispatches to both sub-hooks
120
+ - Return is `useMemo`-wrapped for referential stability
121
+
122
+ **useAgentSession**: Session lifecycle + config
123
+ - `createSession()`: Build config, inject API keys, initialize + newSession
124
+ - `setConfigOption()`: Optimistic update + rollback on error
125
+ - `setMode()` / `setModel()`: Legacy API (deprecated, still used by many agents)
126
+ - Session-level update handler (commands, mode, config, usage, process_error)
127
+ - Uses `sessionRef` pattern to stabilize callback deps
128
+
129
+ **useAgentMessages**: Messaging + streaming + permissions
130
+ - `sendMessage()`: Prepare (auto-mention, path conversion) → send via AcpClient
131
+ - RAF batching: streaming updates accumulated per-frame via `requestAnimationFrame`
132
+ - Tool call index: `Map<string, number>` for O(1) upsert
133
+ - `ignoreUpdatesRef`: suppresses history replay during session/load
134
+ - Permission: `activePermission` (useMemo derivation), approve/reject callbacks
135
+
136
+ **useSuggestions**: @mention + /command (unified)
137
+ - Mention detection, note searching, dropdown interaction
138
+ - Slash command filtering and selection
139
+ - Auto-mention toggle coordination (slash commands disable auto-mention)
140
+ - Return is `useMemo`-wrapped (mentions + commands objects)
141
+
142
+ **useChatActions**: Business callbacks
143
+ - handleSendMessage, handleNewChat, handleExportChat, handleRestartAgent, etc.
144
+ - Uses individual method deps (not whole agent object) for stability
145
+ - Owns restoredMessage and agentUpdateNotification state
146
+
147
+ **useSessionHistory**: Session persistence
148
+ - `restoreSession()`: Load/resume with local message fallback
149
+ - `forkSession()`: Create new branch from existing session
150
+ - 5-minute cache with invalidation
151
+ - Return is `useMemo`-wrapped
152
+
153
+ **useHistoryModal**: Modal lifecycle
154
+ - Lazy modal creation, props synchronization
155
+ - Session operation callbacks (restore, fork, delete)
156
+
157
+ ### ACP Client (`acp/acp-client.ts`) + ACP Handler (`acp/acp-handler.ts`)
158
+
159
+ **AcpClient** — UI-facing API and process lifecycle:
160
+ - spawn() with login shell, JSON-RPC via ndJsonStream
161
+ - initialize() → newSession() → sendPrompt() → cancel() → disconnect()
162
+ - Session management: listSessions, loadSession, resumeSession, forkSession
163
+ - Owns PermissionManager, TerminalManager, AcpHandler
164
+ - `currentSessionId` set before `await` in loadSession/resumeSession to prevent replay filtering
165
+ - Single exit point: `onSessionUpdate` (multiple listeners via Set)
166
+
167
+ **AcpHandler** — SDK event receiver:
168
+ - sessionUpdate: converts ACP types → domain types → broadcast to listeners
169
+ - sessionId filter: only emits updates matching `currentSessionId`
170
+ - requestPermission → PermissionManager
171
+ - Terminal operations → TerminalManager
172
+
173
+ ### Services (`services/`)
174
+
175
+ **VaultService**: Vault access + file index + fuzzy search + CM6 selection tracking
176
+ **SettingsService**: Reactive settings store (observer pattern for useSyncExternalStore). Session storage delegated to SessionStorage.
177
+ **SessionStorage**: Session metadata CRUD (in plugin settings) + message file I/O (sessions/*.json)
178
+ **settings-normalizer**: Validation helpers (str, bool, num, enumVal, obj, strRecord, xyPoint) + toAgentConfig + parseChatFontSize
179
+ **session-helpers**: Pure functions — buildAgentConfigWithApiKey, findAgentSettings, getAvailableAgents
180
+ **session-state**: Pure functions — applyLegacyValue, tryRestoreConfigOption, restoreLegacyConfig
181
+ **message-state**: Pure functions — applySingleUpdate, applyUpsertToolCall, mergeToolCallContent, findActivePermission, selectOption
182
+ **message-sender**: Pure functions — preparePrompt (embedded context vs XML text, shared helpers), sendPreparedPrompt (auth retry)
183
+
184
+ ## Types
185
+
186
+ ### SessionUpdate (`types/session.ts`)
187
+ Union type for all session update events from the agent:
188
+
189
+ ```typescript
190
+ type SessionUpdate =
191
+ | AgentMessageChunk // Text chunk from agent's response
192
+ | AgentThoughtChunk // Text chunk from agent's reasoning
193
+ | UserMessageChunk // Text chunk from user message (session/load)
194
+ | ToolCall // New tool call event
195
+ | ToolCallUpdate // Update to existing tool call
196
+ | Plan // Agent's task plan
197
+ | AvailableCommandsUpdate // Slash commands changed
198
+ | CurrentModeUpdate // Mode changed
199
+ | SessionInfoUpdate // Session metadata changed
200
+ | UsageUpdate // Context window usage
201
+ | ConfigOptionUpdate // Config options changed
202
+ | ProcessErrorUpdate; // Process-level error (spawn failure, command not found)
203
+ ```
204
+
205
+ ### Key Interfaces
206
+
207
+ ```typescript
208
+ // services/vault-service.ts
209
+ interface IVaultAccess {
210
+ readNote(path: string): Promise<string>;
211
+ searchNotes(query: string): Promise<NoteMetadata[]>;
212
+ getActiveNote(): Promise<NoteMetadata | null>;
213
+ listNotes(): Promise<NoteMetadata[]>;
214
+ }
215
+
216
+ // services/settings-service.ts
217
+ interface ISettingsAccess {
218
+ getSnapshot(): AgentClientPluginSettings;
219
+ updateSettings(updates: Partial<AgentClientPluginSettings>): Promise<void>;
220
+ subscribe(listener: () => void): () => void;
221
+ // Session storage methods (delegated to SessionStorage internally)
222
+ saveSession(info: SavedSessionInfo): Promise<void>;
223
+ getSavedSessions(agentId?: string, cwd?: string): SavedSessionInfo[];
224
+ deleteSession(sessionId: string): Promise<void>;
225
+ saveSessionMessages(sessionId: string, agentId: string, messages: ChatMessage[]): Promise<void>;
226
+ loadSessionMessages(sessionId: string): Promise<ChatMessage[] | null>;
227
+ deleteSessionMessages(sessionId: string): Promise<void>;
228
+ }
229
+ ```
230
+
231
+ ## Development Rules
232
+
233
+ ### Architecture
234
+ 1. **useAgent as facade**: Composes useAgentSession + useAgentMessages. ChatPanel calls useAgent, not sub-hooks directly.
235
+ 2. **Services have zero React imports**: Pure functions and classes in `services/`. No useState, useCallback, React.Dispatch, etc.
236
+ 3. **ACP isolation**: All `@agentclientprotocol/sdk` imports confined to `acp/`. AcpClient is UI-facing, AcpHandler is SDK-facing.
237
+ 4. **Types have zero deps**: No `obsidian`, no SDK, no React in `types/`
238
+ 5. **Single event channel**: All agent events (messages, session updates, permissions, errors) flow through `onSessionUpdate`. No special callback paths.
239
+ 6. **Context for services**: plugin, acpClient, vaultService, settingsService via ChatContext
240
+
241
+ ### Performance Patterns
242
+ 1. **useMemo for return stability**: useAgent, useSuggestions, useSessionHistory wrap return objects in useMemo to prevent cascading re-renders
243
+ 2. **sessionRef pattern**: useAgentSession stores session in useRef for callback access without adding session to deps
244
+ 3. **Individual method deps**: useChatActions uses `agent.sendMessage` not `agent` as deps — prevents callback recreation when unrelated state changes
245
+ 4. **Workspace event refs**: ChatPanel stores event handler callbacks in refs, keeping useEffect deps minimal
246
+ 5. **RAF batching**: useAgentMessages batches streaming updates per animation frame (~60fps) instead of per-chunk
247
+ 6. **React.memo**: MessageBubble, ToolCallBlock, TerminalBlock wrapped for skip-render optimization
248
+ 7. **Virtual scroll**: MessageList uses @tanstack/react-virtual for large conversations
249
+ 8. **O(1) tool call index**: Map<string, number> for tool call upsert without linear scan
250
+
251
+ ### Obsidian Plugin Review (CRITICAL)
252
+ 1. No innerHTML/outerHTML - use createEl/createDiv/createSpan
253
+ 2. NO detach leaves in onunload (antipattern)
254
+ 3. Styles in CSS only - no JS style manipulation
255
+ 4. Use Platform interface - not process.platform
256
+ 5. Minimize `any` - use proper types
257
+
258
+ ### Naming Conventions
259
+ - Types: `kebab-case.ts` in `types/`
260
+ - ACP: `kebab-case.ts` in `acp/`
261
+ - Services: `kebab-case.ts` in `services/`
262
+ - Hooks: `use*.ts` in `hooks/`
263
+ - Components: `PascalCase.tsx` in `ui/`
264
+ - Utils: `kebab-case.ts` in `utils/`
265
+
266
+ ### Code Patterns
267
+ 1. React hooks for state management
268
+ 2. useCallback/useMemo for performance (see Performance Patterns above)
269
+ 3. useRef for cleanup function access and stale closure prevention
270
+ 4. Error handling: try-catch async ops
271
+ 5. Logging: Logger class (respects debugMode). Avoid excessive per-keystroke logging.
272
+ 6. **Upsert pattern**: Use `setMessages` functional updates to avoid race conditions with tool_call updates
273
+ 7. **Ref pattern for callbacks**: IChatViewContainer and workspace event handlers use refs for latest values
274
+ 8. **Context value stability**: ChatContext value created once (service instances), wrapped in useMemo
275
+ 9. **Stable empty arrays**: Use module-level constants (e.g., `EMPTY_COMMANDS`) instead of inline `[]` in hook args
276
+
277
+ ## Common Tasks
278
+
279
+ ### Add New Feature Hook
280
+ 1. Create `hooks/use[Feature].ts`
281
+ 2. Define state with useState/useReducer
282
+ 3. Export functions and state
283
+ 4. Call the hook in `ui/ChatPanel.tsx`
284
+ 5. Pass state/callbacks to child components as props
285
+ 6. Wrap return object in `useMemo` if passed as dependency to other hooks
286
+
287
+ ### Add Agent Type
288
+ 1. Add settings type in `types/agent.ts`
289
+ 2. Add config and defaults in `plugin.ts`
290
+ 3. Add API key injection in `services/session-helpers.ts`
291
+ 4. Update `ui/SettingsTab.ts` for configuration UI
292
+
293
+ ### Modify Message Types
294
+ 1. Update `ChatMessage`/`MessageContent` in `types/chat.ts`
295
+ 2. If adding new session update type:
296
+ - Add to `SessionUpdate` union in `types/session.ts`
297
+ - Handle in `hooks/useAgentMessages.ts` (for message-level) or `hooks/useAgentSession.ts` (for session-level)
298
+ 3. Update `acp/acp-handler.ts` `sessionUpdate()` to emit the new type
299
+ 4. Update `ui/MessageBubble.tsx` `ContentBlock` to render new type
300
+
301
+ ### Add New Session Update Type
302
+ 1. Define interface in `types/session.ts`
303
+ 2. Add to `SessionUpdate` union type
304
+ 3. Handle in `hooks/useAgentSession.ts` `handleSessionUpdate()` (for session-level)
305
+ 4. Or handle via `applySingleUpdate()` in `services/message-state.ts` (for message-level)
306
+ 5. No routing needed in ChatPanel — useAgent handles dispatch internally
307
+
308
+ ### Debug
309
+ 1. Settings → Developer Settings → Debug Mode ON
310
+ 2. Open DevTools (Cmd+Option+I / Ctrl+Shift+I)
311
+ 3. Filter logs: `[AcpClient]`, `[AcpHandler]`, `[PermissionManager]`, `[VaultService]`
312
+
313
+ ## ACP Protocol
314
+
315
+ **Communication**: JSON-RPC 2.0 over stdin/stdout
316
+
317
+ **Methods**: initialize, newSession, authenticate, prompt, cancel, setSessionConfigOption
318
+ **Notifications**: session/update (agent_message_chunk, agent_thought_chunk, user_message_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update, session_info_update, usage_update, config_option_update)
319
+ **Requests**: requestPermission
320
+ **Session Management** (unstable): session/list, session/load, session/resume, session/fork
321
+
322
+ **Agents**:
323
+ - Claude Code: `@agentclientprotocol/claude-agent-acp` (ANTHROPIC_API_KEY)
324
+ - Codex: `@zed-industries/codex-acp` (OPENAI_API_KEY)
325
+ - Gemini CLI: `@google/gemini-cli` (GEMINI_API_KEY)
326
+ - Custom: Any ACP-compatible agent
327
+
328
+ ---
329
+
330
+ **Last Updated**: April 2026 | **Architecture**: useAgent facade + sub-hooks | **Version**: 0.10.0-preview.1