Add spec for deeper integration
This commit is contained in:
165
openspec/changes/add-workspace-context-injection/design.md
Normal file
165
openspec/changes/add-workspace-context-injection/design.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# Design: Workspace Context Injection
|
||||
|
||||
## Context
|
||||
|
||||
The plugin embeds OpenCode in an iframe and spawns a local server. OpenCode has an SDK and HTTP API that allows programmatic interaction with sessions. Obsidian's Workspace API provides access to open files, emits events when the workspace changes, and provides access to the active editor's selection.
|
||||
|
||||
**Stakeholders:** Users who want AI to be aware of their open notes and selected text without manual input.
|
||||
|
||||
**Constraints:**
|
||||
- Must not overload the context window with repeated injections
|
||||
- Must work with the existing ProcessManager and view lifecycle
|
||||
- Desktop-only (uses Node.js APIs)
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Automatically provide OpenCode with awareness of open notes
|
||||
- Include currently selected text for immediate context
|
||||
- Keep context window clean (no accumulation of stale context)
|
||||
- Minimal performance impact (debounced updates)
|
||||
- User control via settings (enabled by default)
|
||||
|
||||
**Non-Goals:**
|
||||
- Injecting full file contents (only paths + selection)
|
||||
- Real-time synchronization with every keystroke
|
||||
- Mobile support
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Use OpenCode SDK for API communication
|
||||
**What:** Add `@opencode-ai/sdk` as a dependency and use `createOpencodeClient()` to interact with the server.
|
||||
|
||||
**Why:** Type-safe API, officially supported, handles serialization and error handling.
|
||||
|
||||
**Alternatives considered:**
|
||||
- Direct fetch calls: Simpler but no type safety, more error-prone
|
||||
- postMessage to iframe: Not supported by OpenCode web UI
|
||||
|
||||
### Decision 2: Revert + Re-inject pattern for context updates
|
||||
**What:** Track the message ID of injected context. Before injecting new context, revert the previous message using `session.revert()`, then inject fresh context.
|
||||
|
||||
**Why:** Prevents accumulation of stale context messages that would bloat the context window and confuse the AI.
|
||||
|
||||
**Alternatives considered:**
|
||||
- Append only: Would accumulate redundant messages
|
||||
- System prompt field: Unclear if it replaces or appends
|
||||
- One-time injection: Context becomes stale if user opens/closes files
|
||||
|
||||
### Decision 3: Debounce workspace events (2 seconds)
|
||||
**What:** Use Obsidian's `debounce()` utility with a 2-second delay before sending context updates.
|
||||
|
||||
**Why:** Rapid file switching (e.g., using Cmd+Tab or closing multiple tabs) would otherwise flood the server with API calls.
|
||||
|
||||
### Decision 4: Inject paths + selected text, not full content
|
||||
**What:** Send file paths (e.g., `Notes/Project.md`) and the currently selected text (if any), but not full file contents.
|
||||
|
||||
**Why:**
|
||||
- Keeps context concise and within token limits
|
||||
- Users control what's "in scope" by opening/closing files
|
||||
- Selected text provides immediate, relevant context without overwhelming
|
||||
- Full content injection could easily exceed context limits
|
||||
|
||||
### Decision 5: Track context per session
|
||||
**What:** Maintain a `Map<sessionId, messageId>` to track injected context for each session.
|
||||
|
||||
**Why:** OpenCode may have multiple sessions. Each session needs its own context tracking to properly revert previous injections.
|
||||
|
||||
### Decision 6: Include selection source file
|
||||
**What:** When including selected text, also indicate which file it's from.
|
||||
|
||||
**Why:** Helps the AI understand the context of the selection (e.g., "Selected from Daily/2026-01-12.md").
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Obsidian Plugin │
|
||||
│ │
|
||||
│ ┌─────────────────┐ ┌──────────────────┐ │
|
||||
│ │ WorkspaceContext│ │ OpenCodeClient │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ - getOpenPaths()│ │ - updateContext()│ │
|
||||
│ │ - getSelection()│ │ - revert + inject│ │
|
||||
│ │ - formatContext │ │ │ │
|
||||
│ └────────┬────────┘ └────────┬─────────┘ │
|
||||
│ │ │ │
|
||||
│ └──────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────▼──────────┐ │
|
||||
│ │ main.ts │ │
|
||||
│ │ │ │
|
||||
│ │ - workspace events │ │
|
||||
│ │ - editor-change │ │
|
||||
│ │ - debounced updates │ │
|
||||
│ └──────────┬──────────┘ │
|
||||
│ │ │
|
||||
└──────────────────────┼──────────────────────────────────────┘
|
||||
│ HTTP (SDK)
|
||||
┌────────▼────────┐
|
||||
│ OpenCode Server │
|
||||
│ │
|
||||
│ - session.prompt│
|
||||
│ - session.revert│
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. User opens/closes files or changes selection in Obsidian
|
||||
2. Workspace emits `active-leaf-change`, `layout-change`, or `editor-change` event
|
||||
3. Debouncer waits 2 seconds for activity to settle
|
||||
4. `WorkspaceContext` collects:
|
||||
a. `getOpenNotePaths()` - current open files
|
||||
b. `getSelectedText()` - current selection (if any)
|
||||
5. `OpenCodeClient.updateContext()`:
|
||||
a. Gets current session ID from server
|
||||
b. Reverts previous context message (if tracked)
|
||||
c. Injects new context with `noReply: true`
|
||||
d. Stores new message ID for future revert
|
||||
6. OpenCode AI now has updated context for next interaction
|
||||
|
||||
## Context Format
|
||||
|
||||
```
|
||||
<system-reminder>
|
||||
Currently open notes in Obsidian:
|
||||
- Daily/2026-01-12.md
|
||||
- Projects/Feature-Spec.md
|
||||
- Reference/API-Docs.md
|
||||
|
||||
Selected text (from Projects/Feature-Spec.md):
|
||||
"""
|
||||
The plugin SHALL inject workspace context into OpenCode sessions.
|
||||
This includes both open file paths and selected text.
|
||||
"""
|
||||
</system-reminder>
|
||||
```
|
||||
|
||||
When no text is selected, the "Selected text" section is omitted.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Revert fails (message already gone) | Catch error, continue with fresh inject |
|
||||
| Session changes between revert and inject | Track per-session, clear tracking on session change |
|
||||
| Server not running when events fire | Check `getProcessState() === "running"` before attempting |
|
||||
| SDK adds bundle size | SDK is lightweight; alternative (fetch) adds complexity |
|
||||
| Message ID tracking lost on plugin reload | Acceptable - next update injects fresh context, old message becomes orphaned but harmless |
|
||||
| Large selection could bloat context | Truncate selection to reasonable limit (e.g., 2000 chars) |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
No migration needed. New feature enabled by default but can be disabled in settings.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. ~~Should we also inject the active file distinctly (e.g., "Currently editing: X.md")?~~
|
||||
- Resolved: The selected text section includes the source file, which serves this purpose.
|
||||
|
||||
2. Should context be injected on view open or only on workspace changes?
|
||||
- Decision: Both. Initial injection when view opens, then updates on changes.
|
||||
|
||||
3. Should selection changes trigger immediate updates or use the same debounce?
|
||||
- Decision: Use same 2-second debounce to avoid excessive updates during text selection.
|
||||
26
openspec/changes/add-workspace-context-injection/proposal.md
Normal file
26
openspec/changes/add-workspace-context-injection/proposal.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Change: Add Workspace Context Injection
|
||||
|
||||
## Why
|
||||
|
||||
Users working in Obsidian have multiple notes open that provide context for their AI interactions. Currently, OpenCode has no awareness of which notes are open in Obsidian or what text the user has selected, requiring users to manually reference files and copy/paste selections. By automatically injecting the list of open notes and the currently selected text into OpenCode sessions, the AI gains valuable context about the user's current focus, improving response relevance without manual effort.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add integration with OpenCode SDK (`@opencode-ai/sdk`) to communicate with the server
|
||||
- Collect currently open note paths from Obsidian's workspace API
|
||||
- Collect currently selected text from the active editor (if any)
|
||||
- Inject open notes and selected text as context into the current OpenCode session using `session.prompt({ noReply: true })`
|
||||
- Use revert + re-inject pattern to prevent context accumulation (each update replaces previous context)
|
||||
- Debounce workspace change events to avoid flooding the server
|
||||
- Add settings to enable/disable the feature (enabled by default) and limit the number of notes included
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: `001-mvp-opencode-embed`
|
||||
- Affected code:
|
||||
- `package.json` - new SDK dependency
|
||||
- `src/types.ts` - new settings fields
|
||||
- `src/main.ts` - workspace event registration, context injection orchestration
|
||||
- `src/SettingsTab.ts` - new settings UI
|
||||
- `src/OpenCodeClient.ts` - **new file** for SDK wrapper
|
||||
- `src/WorkspaceContext.ts` - **new file** for workspace data collection (open notes + selection)
|
||||
@@ -0,0 +1,123 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Workspace Context Collection
|
||||
The plugin SHALL collect the file paths of all currently open markdown notes in the Obsidian workspace.
|
||||
|
||||
#### Scenario: Collect open note paths
|
||||
- **WHEN** the plugin needs to gather workspace context
|
||||
- **THEN** it retrieves all leaves of type "markdown" from the workspace
|
||||
- **AND** extracts the file path from each leaf's view
|
||||
- **AND** deduplicates paths (same file may be open in multiple panes)
|
||||
|
||||
### Requirement: Selected Text Collection
|
||||
The plugin SHALL collect the currently selected text from the active editor, if any selection exists.
|
||||
|
||||
#### Scenario: Collect selected text with source
|
||||
- **WHEN** the plugin needs to gather workspace context
|
||||
- **AND** text is selected in the active editor
|
||||
- **THEN** it retrieves the selected text content
|
||||
- **AND** identifies the source file path of the selection
|
||||
|
||||
#### Scenario: No selection present
|
||||
- **WHEN** the plugin needs to gather workspace context
|
||||
- **AND** no text is selected in the active editor
|
||||
- **THEN** the selected text portion of the context is omitted
|
||||
|
||||
#### Scenario: Selection truncation
|
||||
- **WHEN** the selected text exceeds `maxSelectionLength` characters
|
||||
- **THEN** the selection is truncated to the configured limit
|
||||
- **AND** an indicator is added showing truncation occurred
|
||||
|
||||
### Requirement: Context Injection to OpenCode
|
||||
The plugin SHALL inject the workspace context (open notes and selected text) into the current OpenCode session.
|
||||
|
||||
#### Scenario: Inject context on workspace change
|
||||
- **WHEN** the user opens, closes, or switches between notes
|
||||
- **AND** the setting "Inject workspace context" is enabled
|
||||
- **AND** the OpenCode server is running
|
||||
- **THEN** the plugin sends the workspace context to the current OpenCode session
|
||||
- **AND** the context is injected using the SDK with `noReply: true` (no AI response triggered)
|
||||
|
||||
#### Scenario: Inject context on selection change
|
||||
- **WHEN** the user changes their text selection in an editor
|
||||
- **AND** the setting is enabled
|
||||
- **AND** the OpenCode server is running
|
||||
- **THEN** the plugin updates the workspace context (debounced)
|
||||
|
||||
#### Scenario: Initial context injection
|
||||
- **WHEN** the OpenCode server transitions to running state
|
||||
- **AND** the setting is enabled
|
||||
- **THEN** the plugin injects the current workspace context
|
||||
|
||||
### Requirement: Context Replacement via Revert
|
||||
The plugin SHALL replace previous context injections rather than accumulating them.
|
||||
|
||||
#### Scenario: Revert before re-inject
|
||||
- **WHEN** the plugin injects new context
|
||||
- **AND** a previous context message exists for the session
|
||||
- **THEN** the plugin reverts the previous context message using `session.revert()`
|
||||
- **AND** then injects the new context
|
||||
- **AND** stores the new message ID for future revert
|
||||
|
||||
#### Scenario: Revert failure handling
|
||||
- **WHEN** the revert operation fails (message already gone or session changed)
|
||||
- **THEN** the plugin continues with fresh context injection
|
||||
- **AND** logs the error to console for debugging
|
||||
|
||||
### Requirement: Debounced Context Updates
|
||||
The plugin SHALL debounce workspace change events to prevent excessive API calls.
|
||||
|
||||
#### Scenario: Rapid file switching
|
||||
- **WHEN** the user rapidly opens or closes multiple files
|
||||
- **THEN** the plugin waits 2 seconds after the last change before sending context update
|
||||
- **AND** only one API call is made for the batch of changes
|
||||
|
||||
#### Scenario: Rapid selection changes
|
||||
- **WHEN** the user is actively selecting text (dragging)
|
||||
- **THEN** the plugin waits 2 seconds after selection stabilizes before updating context
|
||||
|
||||
### Requirement: Context Injection Settings
|
||||
The plugin SHALL provide settings to control workspace context injection behavior.
|
||||
|
||||
#### Scenario: Enable/disable toggle
|
||||
- **WHEN** the user disables "Inject workspace context"
|
||||
- **THEN** the plugin does not register workspace event listeners
|
||||
- **AND** no context is injected into OpenCode sessions
|
||||
|
||||
#### Scenario: Enabled by default
|
||||
- **WHEN** the plugin is installed fresh
|
||||
- **THEN** the "Inject workspace context" setting defaults to enabled
|
||||
|
||||
#### Scenario: Limit number of notes
|
||||
- **WHEN** more than `maxNotesInContext` notes are open
|
||||
- **THEN** the plugin includes only the first N paths
|
||||
- **AND** the default limit is 20 notes
|
||||
|
||||
#### Scenario: Limit selection length
|
||||
- **WHEN** the selected text exceeds `maxSelectionLength` characters
|
||||
- **THEN** the plugin truncates the selection
|
||||
- **AND** the default limit is 2000 characters
|
||||
|
||||
### Requirement: Context Format
|
||||
The plugin SHALL format the context as a system reminder containing file paths and optional selected text.
|
||||
|
||||
#### Scenario: Context message format with selection
|
||||
- **WHEN** context is injected
|
||||
- **AND** text is selected
|
||||
- **THEN** the message is wrapped in `<system-reminder>` tags
|
||||
- **AND** includes a header "Currently open notes in Obsidian:"
|
||||
- **AND** lists each file path as a bullet point
|
||||
- **AND** includes a "Selected text (from <filepath>):" section
|
||||
- **AND** wraps the selected text in triple quotes
|
||||
|
||||
#### Scenario: Context message format without selection
|
||||
- **WHEN** context is injected
|
||||
- **AND** no text is selected
|
||||
- **THEN** the message contains only the open notes section
|
||||
- **AND** the selected text section is omitted
|
||||
|
||||
#### Scenario: Empty context
|
||||
- **WHEN** no markdown files are open
|
||||
- **AND** no text is selected
|
||||
- **THEN** no context message is injected
|
||||
- **AND** any previous context message is reverted
|
||||
60
openspec/changes/add-workspace-context-injection/tasks.md
Normal file
60
openspec/changes/add-workspace-context-injection/tasks.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Tasks: Add Workspace Context Injection
|
||||
|
||||
## 1. Dependencies
|
||||
|
||||
- [ ] 1.1 Add `@opencode-ai/sdk` to package.json dependencies
|
||||
- [ ] 1.2 Run `bun install` to install the SDK
|
||||
|
||||
## 2. Types and Settings
|
||||
|
||||
- [ ] 2.1 Add `injectWorkspaceContext: boolean` to `OpenCodeSettings` interface (default: `true`)
|
||||
- [ ] 2.2 Add `maxNotesInContext: number` to `OpenCodeSettings` interface (default: `20`)
|
||||
- [ ] 2.3 Add `maxSelectionLength: number` to `OpenCodeSettings` interface (default: `2000`)
|
||||
- [ ] 2.4 Update `DEFAULT_SETTINGS` with new values
|
||||
|
||||
## 3. OpenCode Client Module
|
||||
|
||||
- [ ] 3.1 Create `src/OpenCodeClient.ts` with SDK client wrapper
|
||||
- [ ] 3.2 Implement `getCurrentSessionId()` method to get active session
|
||||
- [ ] 3.3 Implement `updateContext()` method with revert + inject logic
|
||||
- [ ] 3.4 Add `contextMessageIds` Map for tracking injected messages per session
|
||||
- [ ] 3.5 Add error handling for API failures (silent catch, log to console)
|
||||
|
||||
## 4. Workspace Context Module
|
||||
|
||||
- [ ] 4.1 Create `src/WorkspaceContext.ts` for collecting workspace context
|
||||
- [ ] 4.2 Implement `getOpenNotePaths()` using `getLeavesOfType("markdown")`
|
||||
- [ ] 4.3 Implement `getSelectedText()` to get current editor selection with source file
|
||||
- [ ] 4.4 Implement `formatContext()` to generate the combined context string
|
||||
- [ ] 4.5 Add deduplication for files open in multiple panes
|
||||
- [ ] 4.6 Add truncation for selections exceeding `maxSelectionLength`
|
||||
|
||||
## 5. Main Plugin Integration
|
||||
|
||||
- [ ] 5.1 Import `OpenCodeClient` and `WorkspaceContext` in main.ts
|
||||
- [ ] 5.2 Initialize `WorkspaceContext` in `onload()`
|
||||
- [ ] 5.3 Create debounced `updateOpenCodeContext()` method (2 second delay)
|
||||
- [ ] 5.4 Register `active-leaf-change` event listener (conditional on setting)
|
||||
- [ ] 5.5 Register `layout-change` event listener (conditional on setting)
|
||||
- [ ] 5.6 Register `editor-change` event listener for selection changes (conditional on setting)
|
||||
- [ ] 5.7 Initialize `OpenCodeClient` lazily when first needed
|
||||
- [ ] 5.8 Add server running check before attempting context updates
|
||||
- [ ] 5.9 Trigger initial context injection when server reaches running state
|
||||
|
||||
## 6. Settings UI
|
||||
|
||||
- [ ] 6.1 Add toggle for "Inject workspace context" in SettingsTab
|
||||
- [ ] 6.2 Add slider for "Max notes in context" (1-50 range)
|
||||
- [ ] 6.3 Add slider or input for "Max selection length" (500-5000 range)
|
||||
- [ ] 6.4 Add descriptive text explaining the feature includes open notes and selected text
|
||||
|
||||
## 7. Testing
|
||||
|
||||
- [ ] 7.1 Manual test: Open multiple notes, verify context appears in OpenCode
|
||||
- [ ] 7.2 Manual test: Select text, verify selection appears in context with source file
|
||||
- [ ] 7.3 Manual test: Close notes, verify context updates (old reverted, new injected)
|
||||
- [ ] 7.4 Manual test: Clear selection, verify selection section is removed
|
||||
- [ ] 7.5 Manual test: Disable setting, verify no context injection
|
||||
- [ ] 7.6 Manual test: Server not running, verify no errors thrown
|
||||
- [ ] 7.7 Manual test: Large selection, verify truncation works
|
||||
- [ ] 7.8 Build and verify no TypeScript errors
|
||||
Reference in New Issue
Block a user