@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.
- package/.claude/hooks/gh-setup.sh +49 -0
- package/.claude/settings.json +15 -0
- package/.claude/skills/release-notes/SKILL.md +331 -0
- package/.editorconfig +10 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +59 -0
- package/.github/copilot-instructions.md +45 -0
- package/.github/pull_request_template.md +32 -0
- package/.github/workflows/ci.yaml +25 -0
- package/.github/workflows/docs.yml +58 -0
- package/.github/workflows/relay_to_openclaw.yml +59 -0
- package/.github/workflows/release.yaml +45 -0
- package/.prettierignore +10 -0
- package/.prettierrc +13 -0
- package/.vscode/extensions.json +7 -0
- package/.vscode/settings.json +37 -0
- package/.zed/settings.json +42 -0
- package/AGENTS.md +330 -0
- package/ARCHITECTURE.md +390 -0
- package/CONTRIBUTING.md +216 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.ja.md +121 -0
- package/README.md +125 -0
- package/docs/.vitepress/config.mts +124 -0
- package/docs/.vitepress/theme/custom.css +111 -0
- package/docs/.vitepress/theme/index.ts +4 -0
- package/docs/agent-setup/claude-code.md +84 -0
- package/docs/agent-setup/codex.md +76 -0
- package/docs/agent-setup/custom-agents.md +67 -0
- package/docs/agent-setup/gemini-cli.md +99 -0
- package/docs/agent-setup/index.md +34 -0
- package/docs/announcements/gemini-cli-deprecation.md +73 -0
- package/docs/getting-started/index.md +78 -0
- package/docs/getting-started/quick-start.md +38 -0
- package/docs/help/faq.md +181 -0
- package/docs/help/troubleshooting.md +221 -0
- package/docs/index.md +63 -0
- package/docs/public/apple-touch-icon.png +0 -0
- package/docs/public/demo.mp4 +0 -0
- package/docs/public/favicon-16x16.png +0 -0
- package/docs/public/favicon-32x32.png +0 -0
- package/docs/public/favicon.ico +0 -0
- package/docs/public/images/editing.webp +0 -0
- package/docs/public/images/export.webp +0 -0
- package/docs/public/images/floating-chat-button.webp +0 -0
- package/docs/public/images/floating-chat-instance-menu.webp +0 -0
- package/docs/public/images/floating-chat-view.webp +0 -0
- package/docs/public/images/mode-selection.webp +0 -0
- package/docs/public/images/model-selection.webp +0 -0
- package/docs/public/images/multi-session.webp +0 -0
- package/docs/public/images/remove-image.webp +0 -0
- package/docs/public/images/ribbon-icon.webp +0 -0
- package/docs/public/images/selection-context.gif +0 -0
- package/docs/public/images/sending-images.webp +0 -0
- package/docs/public/images/sending-messages.webp +0 -0
- package/docs/public/images/session-history-button.webp +0 -0
- package/docs/public/images/slash-commands-1.webp +0 -0
- package/docs/public/images/slash-commands-2.webp +0 -0
- package/docs/public/images/switch-agent.webp +0 -0
- package/docs/public/images/switch-default-agent.webp +0 -0
- package/docs/public/images/temporary-disable.gif +0 -0
- package/docs/reference/acp-support.md +110 -0
- package/docs/usage/chat-export.md +80 -0
- package/docs/usage/commands.md +51 -0
- package/docs/usage/context-files.md +57 -0
- package/docs/usage/editing.md +69 -0
- package/docs/usage/floating-chat.md +84 -0
- package/docs/usage/index.md +97 -0
- package/docs/usage/mcp-tools.md +33 -0
- package/docs/usage/mentions.md +70 -0
- package/docs/usage/mode-selection.md +28 -0
- package/docs/usage/model-selection.md +32 -0
- package/docs/usage/multi-session.md +68 -0
- package/docs/usage/sending-images.md +64 -0
- package/docs/usage/session-history.md +91 -0
- package/docs/usage/slash-commands.md +44 -0
- package/esbuild.config.mjs +49 -0
- package/eslint.config.mjs +25 -0
- package/main.js +228 -0
- package/manifest.json +11 -0
- package/package.json +52 -0
- package/src/acp/acp-client.ts +921 -0
- package/src/acp/acp-handler.ts +252 -0
- package/src/acp/permission-handler.ts +282 -0
- package/src/acp/terminal-handler.ts +264 -0
- package/src/acp/type-converter.ts +272 -0
- package/src/hooks/useAgent.ts +250 -0
- package/src/hooks/useAgentMessages.ts +470 -0
- package/src/hooks/useAgentSession.ts +544 -0
- package/src/hooks/useChatActions.ts +400 -0
- package/src/hooks/useHistoryModal.ts +219 -0
- package/src/hooks/useSessionHistory.ts +863 -0
- package/src/hooks/useSettings.ts +19 -0
- package/src/hooks/useSuggestions.ts +342 -0
- package/src/main.ts +9 -0
- package/src/plugin.ts +1126 -0
- package/src/services/chat-exporter.ts +552 -0
- package/src/services/message-sender.ts +755 -0
- package/src/services/message-state.ts +375 -0
- package/src/services/session-helpers.ts +211 -0
- package/src/services/session-state.ts +130 -0
- package/src/services/session-storage.ts +267 -0
- package/src/services/settings-normalizer.ts +255 -0
- package/src/services/settings-service.ts +285 -0
- package/src/services/update-checker.ts +128 -0
- package/src/services/vault-service.ts +558 -0
- package/src/services/view-registry.ts +345 -0
- package/src/types/agent.ts +92 -0
- package/src/types/chat.ts +351 -0
- package/src/types/errors.ts +136 -0
- package/src/types/obsidian-internals.d.ts +14 -0
- package/src/types/session.ts +731 -0
- package/src/ui/ChangeDirectoryModal.ts +137 -0
- package/src/ui/ChatContext.ts +25 -0
- package/src/ui/ChatHeader.tsx +295 -0
- package/src/ui/ChatPanel.tsx +1162 -0
- package/src/ui/ChatView.tsx +348 -0
- package/src/ui/ErrorBanner.tsx +104 -0
- package/src/ui/FloatingButton.tsx +351 -0
- package/src/ui/FloatingChatView.tsx +531 -0
- package/src/ui/InputArea.tsx +1107 -0
- package/src/ui/InputToolbar.tsx +371 -0
- package/src/ui/MessageBubble.tsx +442 -0
- package/src/ui/MessageList.tsx +265 -0
- package/src/ui/PermissionBanner.tsx +61 -0
- package/src/ui/SessionHistoryModal.tsx +821 -0
- package/src/ui/SettingsTab.ts +1337 -0
- package/src/ui/SuggestionPopup.tsx +138 -0
- package/src/ui/TerminalBlock.tsx +107 -0
- package/src/ui/ToolCallBlock.tsx +456 -0
- package/src/ui/shared/AttachmentStrip.tsx +57 -0
- package/src/ui/shared/IconButton.tsx +55 -0
- package/src/ui/shared/MarkdownRenderer.tsx +103 -0
- package/src/ui/view-host.ts +56 -0
- package/src/utils/error-utils.ts +274 -0
- package/src/utils/logger.ts +44 -0
- package/src/utils/mention-parser.ts +129 -0
- package/src/utils/paths.ts +246 -0
- package/src/utils/platform.ts +425 -0
- package/styles.css +2322 -0
- package/tsconfig.json +18 -0
- package/version-bump.mjs +18 -0
- 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
|
package/.prettierignore
ADDED
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,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
|