opencode-mem 2.2.0 → 2.3.1
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/README.md +82 -107
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -85
- package/dist/services/api-handlers.d.ts +1 -3
- package/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +16 -47
- package/dist/services/context.d.ts +1 -1
- package/dist/services/context.d.ts.map +1 -1
- package/dist/services/context.js +1 -10
- package/dist/services/sqlite/connection-manager.d.ts +2 -0
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -1
- package/dist/services/sqlite/connection-manager.js +77 -1
- package/dist/services/user-memory-learning.d.ts +1 -1
- package/dist/services/user-memory-learning.d.ts.map +1 -1
- package/dist/services/user-memory-learning.js +2 -2
- package/dist/services/web-server-worker.js +1 -2
- package/dist/web/app.js +6 -19
- package/dist/web/index.html +0 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# OpenCode Memory
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-mem)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-mem)
|
|
5
|
+
[](https://www.npmjs.com/package/opencode-mem)
|
|
6
|
+
|
|
3
7
|

|
|
4
8
|
|
|
5
9
|
A persistent memory system for AI coding agents that enables long-term context retention across sessions using local vector database technology.
|
|
@@ -11,10 +15,9 @@ OpenCode Memory provides AI coding agents with the ability to remember and recal
|
|
|
11
15
|
## Key Features
|
|
12
16
|
|
|
13
17
|
- **Local Vector Database**: SQLite-based storage with sqlite-vec extension
|
|
14
|
-
- **
|
|
18
|
+
- **Project Memory System**: Persistent storage for project-specific knowledge
|
|
19
|
+
- **User Profile System**: Automatic learning of preferences, patterns, and workflows
|
|
15
20
|
- **Unified Timeline**: Browse memories and prompts together with linking support
|
|
16
|
-
- **Prompt-Memory Linking**: Bidirectional links between prompts and generated memories
|
|
17
|
-
- **User Profile System**: Structured learning with preferences, patterns, workflows, and skill assessment
|
|
18
21
|
- **Web Interface**: Full-featured UI for memory management and search
|
|
19
22
|
- **Auto-Capture System**: Intelligent prompt-based memory extraction
|
|
20
23
|
- **Multi-Provider AI**: Support for OpenAI, Anthropic, and OpenAI-compatible APIs
|
|
@@ -38,6 +41,36 @@ Add the plugin to your OpenCode configuration:
|
|
|
38
41
|
|
|
39
42
|
OpenCode will automatically download and install the plugin on next startup.
|
|
40
43
|
|
|
44
|
+
### macOS Users - IMPORTANT
|
|
45
|
+
|
|
46
|
+
macOS ships with Apple's SQLite which **disables extension loading** for security reasons. You must install and configure Homebrew SQLite:
|
|
47
|
+
|
|
48
|
+
**Step 1: Install Homebrew SQLite**
|
|
49
|
+
```bash
|
|
50
|
+
brew install sqlite
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Step 2: Find the library path**
|
|
54
|
+
```bash
|
|
55
|
+
brew --prefix sqlite
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Step 3: Configure the path**
|
|
59
|
+
|
|
60
|
+
Edit `~/.config/opencode/opencode-mem.jsonc` and add:
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{
|
|
64
|
+
"customSqlitePath": "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Common paths:**
|
|
69
|
+
- **Apple Silicon (M1/M2/M3)**: `/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib`
|
|
70
|
+
- **Intel Mac**: `/usr/local/opt/sqlite/lib/libsqlite3.dylib`
|
|
71
|
+
|
|
72
|
+
The plugin will auto-detect these paths if not configured, but manual configuration is recommended for reliability.
|
|
73
|
+
|
|
41
74
|
### Install from Source
|
|
42
75
|
|
|
43
76
|
```bash
|
|
@@ -52,12 +85,18 @@ bun run build
|
|
|
52
85
|
### Basic Usage
|
|
53
86
|
|
|
54
87
|
```typescript
|
|
55
|
-
|
|
56
|
-
memory({ mode: "
|
|
88
|
+
// Add project memory
|
|
89
|
+
memory({ mode: "add", content: "Project uses microservices architecture" })
|
|
90
|
+
|
|
91
|
+
// Search memories
|
|
92
|
+
memory({ mode: "search", query: "architecture decisions" })
|
|
93
|
+
|
|
94
|
+
// View user profile
|
|
57
95
|
memory({ mode: "profile" })
|
|
58
|
-
```
|
|
59
96
|
|
|
60
|
-
|
|
97
|
+
// List recent memories
|
|
98
|
+
memory({ mode: "list", limit: 10 })
|
|
99
|
+
```
|
|
61
100
|
|
|
62
101
|
### Web Interface
|
|
63
102
|
|
|
@@ -71,13 +110,14 @@ Access at `http://127.0.0.1:4747` to browse memories, view prompt-memory links,
|
|
|
71
110
|
|
|
72
111
|

|
|
73
112
|
|
|
74
|
-
|
|
113
|
+
## Configuration
|
|
75
114
|
|
|
76
115
|
Configuration file: `~/.config/opencode/opencode-mem.jsonc`
|
|
77
116
|
|
|
78
117
|
```jsonc
|
|
79
118
|
{
|
|
80
119
|
"storagePath": "~/.opencode-mem/data",
|
|
120
|
+
"customSqlitePath": "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
|
|
81
121
|
"embeddingModel": "Xenova/nomic-embed-text-v1",
|
|
82
122
|
"webServerEnabled": true,
|
|
83
123
|
"webServerPort": 4747,
|
|
@@ -86,38 +126,48 @@ Configuration file: `~/.config/opencode/opencode-mem.jsonc`
|
|
|
86
126
|
"memoryModel": "gpt-4",
|
|
87
127
|
"memoryApiUrl": "https://api.openai.com/v1",
|
|
88
128
|
"memoryApiKey": "sk-...",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"userProfileMaxPatterns": 15,
|
|
92
|
-
"userProfileMaxWorkflows": 10,
|
|
93
|
-
"userProfileConfidenceDecayDays": 30,
|
|
94
|
-
"userProfileChangelogRetentionCount": 5
|
|
129
|
+
"userProfileAnalysisInterval": 10,
|
|
130
|
+
"maxMemories": 10
|
|
95
131
|
}
|
|
96
132
|
```
|
|
97
133
|
|
|
98
|
-
|
|
134
|
+
See [Configuration Guide](https://github.com/tickernelz/opencode-mem/wiki/Configuration-Guide) for all options.
|
|
99
135
|
|
|
100
|
-
|
|
136
|
+
## Breaking Changes (v2.3)
|
|
101
137
|
|
|
102
|
-
|
|
103
|
-
- Changed: `memory({ mode: "profile" })` returns new structure (preferences/patterns/workflows/skillLevel)
|
|
104
|
-
- Added: 5 new config options for profile management
|
|
105
|
-
- New behavior: User learning creates/updates structured profile instead of individual memories
|
|
106
|
-
- Migration: Existing user memories remain readable but new ones cannot be created
|
|
138
|
+
**User-scoped memories completely removed:**
|
|
107
139
|
|
|
108
|
-
**
|
|
140
|
+
- **Removed**: `scope` parameter from all memory operations
|
|
141
|
+
- **Removed**: `maxProjectMemories` config (use `maxMemories` instead)
|
|
142
|
+
- **Renamed**: `userMemoryAnalysisInterval` → `userProfileAnalysisInterval`
|
|
143
|
+
- **Renamed**: `performUserMemoryLearning()` → `performUserProfileLearning()`
|
|
144
|
+
- **Changed**: All memories are now project-scoped by default
|
|
145
|
+
- **Changed**: User preferences managed exclusively through automatic profile system
|
|
109
146
|
|
|
110
|
-
|
|
147
|
+
**Migration required:**
|
|
148
|
+
```jsonc
|
|
149
|
+
// OLD
|
|
150
|
+
{
|
|
151
|
+
"userMemoryAnalysisInterval": 10,
|
|
152
|
+
"maxMemories": 5,
|
|
153
|
+
"maxProjectMemories": 10
|
|
154
|
+
}
|
|
111
155
|
|
|
112
|
-
|
|
156
|
+
// NEW
|
|
157
|
+
{
|
|
158
|
+
"userProfileAnalysisInterval": 10,
|
|
159
|
+
"maxMemories": 10
|
|
160
|
+
}
|
|
161
|
+
```
|
|
113
162
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
- Prompt-memory linking with cascade delete support
|
|
163
|
+
Remove `scope` parameter from all `memory()` calls:
|
|
164
|
+
```typescript
|
|
165
|
+
// OLD
|
|
166
|
+
memory({ mode: "add", content: "...", scope: "project" })
|
|
119
167
|
|
|
120
|
-
|
|
168
|
+
// NEW
|
|
169
|
+
memory({ mode: "add", content: "..." })
|
|
170
|
+
```
|
|
121
171
|
|
|
122
172
|
## Documentation
|
|
123
173
|
|
|
@@ -126,88 +176,13 @@ For detailed documentation, see the [Wiki](https://github.com/tickernelz/opencod
|
|
|
126
176
|
- [Installation Guide](https://github.com/tickernelz/opencode-mem/wiki/Installation-Guide)
|
|
127
177
|
- [Quick Start](https://github.com/tickernelz/opencode-mem/wiki/Quick-Start)
|
|
128
178
|
- [Configuration Guide](https://github.com/tickernelz/opencode-mem/wiki/Configuration-Guide)
|
|
129
|
-
- [User Profile System](https://github.com/tickernelz/opencode-mem/wiki/User-Profile)
|
|
179
|
+
- [User Profile System](https://github.com/tickernelz/opencode-mem/wiki/User-Profile-System)
|
|
130
180
|
- [Memory Operations](https://github.com/tickernelz/opencode-mem/wiki/Memory-Operations)
|
|
131
181
|
- [Auto-Capture System](https://github.com/tickernelz/opencode-mem/wiki/Auto-Capture-System)
|
|
132
182
|
- [Web Interface](https://github.com/tickernelz/opencode-mem/wiki/Web-Interface)
|
|
133
|
-
- [
|
|
134
|
-
- [Performance Tuning](https://github.com/tickernelz/opencode-mem/wiki/Performance-Tuning)
|
|
183
|
+
- [API Reference](https://github.com/tickernelz/opencode-mem/wiki/API-Reference)
|
|
135
184
|
- [Troubleshooting](https://github.com/tickernelz/opencode-mem/wiki/Troubleshooting)
|
|
136
185
|
|
|
137
|
-
## Features Overview
|
|
138
|
-
|
|
139
|
-
### Memory Scopes
|
|
140
|
-
|
|
141
|
-
- **User Scope**: Cross-project preferences, coding style, communication patterns
|
|
142
|
-
- **Project Scope**: Architecture decisions, technology stack, implementation details
|
|
143
|
-
|
|
144
|
-
### Auto-Capture System
|
|
145
|
-
|
|
146
|
-
Automatically extracts memories from conversations:
|
|
147
|
-
|
|
148
|
-
1. Triggers on session idle
|
|
149
|
-
2. Analyzes last uncaptured prompt and response
|
|
150
|
-
3. Links memory to source prompt
|
|
151
|
-
4. Skips non-technical conversations
|
|
152
|
-
|
|
153
|
-
### User Profile System
|
|
154
|
-
|
|
155
|
-
Builds structured user profile from conversation history (default: every 10 prompts):
|
|
156
|
-
|
|
157
|
-
- **Preferences**: Code style, communication style, tool preferences (with confidence scores)
|
|
158
|
-
- **Patterns**: Recurring topics, problem domains, technical interests (with frequency tracking)
|
|
159
|
-
- **Workflows**: Development sequences, habits, learning style
|
|
160
|
-
- **Skill Level**: Overall and per-domain assessment
|
|
161
|
-
|
|
162
|
-
Profile includes versioning, changelog, and confidence decay mechanism.
|
|
163
|
-
|
|
164
|
-
### Web Interface
|
|
165
|
-
|
|
166
|
-
- Unified timeline of memories and prompts
|
|
167
|
-
- User profile viewer with changelog
|
|
168
|
-
- Visual prompt-memory link indicators
|
|
169
|
-
- Cascade delete for linked items
|
|
170
|
-
- Bulk operations
|
|
171
|
-
- Search and filters
|
|
172
|
-
- Maintenance tools (cleanup, deduplication)
|
|
173
|
-
|
|
174
|
-
## API Reference
|
|
175
|
-
|
|
176
|
-
### Memory Tool
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
memory({ mode: "add", content: "...", scope: "project" })
|
|
180
|
-
memory({ mode: "search", query: "...", scope: "user|project" })
|
|
181
|
-
memory({ mode: "list", scope: "user|project", limit: 10 })
|
|
182
|
-
memory({ mode: "profile" })
|
|
183
|
-
memory({ mode: "forget", memoryId: "..." })
|
|
184
|
-
memory({ mode: "auto-capture-toggle" })
|
|
185
|
-
memory({ mode: "auto-capture-stats" })
|
|
186
|
-
memory({ mode: "capture-now" })
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
**Note**: `scope: "user"` for `add` mode is deprecated in v2.2+.
|
|
190
|
-
|
|
191
|
-
### REST API
|
|
192
|
-
|
|
193
|
-
**Memory & Prompt Management:**
|
|
194
|
-
- `GET /api/memories?scope=project&includePrompts=true` - List memories/prompts
|
|
195
|
-
- `POST /api/memories` - Create memory
|
|
196
|
-
- `PUT /api/memories/:id` - Update memory
|
|
197
|
-
- `DELETE /api/memories/:id?cascade=true` - Delete memory (and linked prompt)
|
|
198
|
-
- `DELETE /api/prompts/:id?cascade=true` - Delete prompt (and linked memory)
|
|
199
|
-
- `POST /api/search` - Vector search
|
|
200
|
-
|
|
201
|
-
**User Profile:**
|
|
202
|
-
- `GET /api/profile` - Get user profile
|
|
203
|
-
- `GET /api/profile/changelog?limit=5` - Get profile changelog
|
|
204
|
-
- `GET /api/profile/snapshot/:changelogId` - Get profile snapshot
|
|
205
|
-
- `POST /api/profile/refresh` - Force profile refresh
|
|
206
|
-
|
|
207
|
-
**Maintenance:**
|
|
208
|
-
- `POST /api/cleanup` - Run cleanup
|
|
209
|
-
- `POST /api/deduplicate` - Run deduplication
|
|
210
|
-
|
|
211
186
|
## Development
|
|
212
187
|
|
|
213
188
|
```bash
|
package/dist/config.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export declare const CONFIG: {
|
|
2
2
|
storagePath: string;
|
|
3
|
+
customSqlitePath: string | undefined;
|
|
3
4
|
embeddingModel: string;
|
|
4
5
|
embeddingDimensions: number;
|
|
5
6
|
embeddingApiUrl: string | undefined;
|
|
6
7
|
embeddingApiKey: string | undefined;
|
|
7
8
|
similarityThreshold: number;
|
|
8
9
|
maxMemories: number;
|
|
9
|
-
maxProjectMemories: number;
|
|
10
10
|
maxProfileItems: number;
|
|
11
11
|
injectProfile: boolean;
|
|
12
12
|
containerTagPrefix: string;
|
|
@@ -27,7 +27,7 @@ export declare const CONFIG: {
|
|
|
27
27
|
autoCleanupRetentionDays: number;
|
|
28
28
|
deduplicationEnabled: boolean;
|
|
29
29
|
deduplicationSimilarityThreshold: number;
|
|
30
|
-
|
|
30
|
+
userProfileAnalysisInterval: number;
|
|
31
31
|
userProfileMaxPreferences: number;
|
|
32
32
|
userProfileMaxPatterns: number;
|
|
33
33
|
userProfileMaxWorkflows: number;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAyXA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;oBAwBb,aAAa,GACb,kBAAkB,GAClB,WAAW;;;;;;;;;;;;;;;;;;;CAyBhB,CAAC;AAEF,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
|
package/dist/config.js
CHANGED
|
@@ -37,8 +37,7 @@ const DEFAULTS = {
|
|
|
37
37
|
embeddingModel: "Xenova/nomic-embed-text-v1",
|
|
38
38
|
embeddingDimensions: 768,
|
|
39
39
|
similarityThreshold: 0.6,
|
|
40
|
-
maxMemories:
|
|
41
|
-
maxProjectMemories: 10,
|
|
40
|
+
maxMemories: 10,
|
|
42
41
|
maxProfileItems: 5,
|
|
43
42
|
injectProfile: true,
|
|
44
43
|
containerTagPrefix: "opencode",
|
|
@@ -55,7 +54,7 @@ const DEFAULTS = {
|
|
|
55
54
|
autoCleanupRetentionDays: 30,
|
|
56
55
|
deduplicationEnabled: true,
|
|
57
56
|
deduplicationSimilarityThreshold: 0.9,
|
|
58
|
-
|
|
57
|
+
userProfileAnalysisInterval: 10,
|
|
59
58
|
userProfileMaxPreferences: 20,
|
|
60
59
|
userProfileMaxPatterns: 15,
|
|
61
60
|
userProfileMaxWorkflows: 10,
|
|
@@ -102,6 +101,22 @@ const CONFIG_TEMPLATE = `{
|
|
|
102
101
|
// Storage location for vector database
|
|
103
102
|
"storagePath": "~/.opencode-mem/data",
|
|
104
103
|
|
|
104
|
+
// ============================================
|
|
105
|
+
// macOS SQLite Extension Loading (REQUIRED FOR macOS)
|
|
106
|
+
// ============================================
|
|
107
|
+
|
|
108
|
+
// macOS users MUST set this to use Homebrew SQLite instead of Apple's SQLite
|
|
109
|
+
// Apple's SQLite disables extension loading which breaks sqlite-vec
|
|
110
|
+
//
|
|
111
|
+
// Common paths:
|
|
112
|
+
// - Homebrew (Intel): "/usr/local/opt/sqlite/lib/libsqlite3.dylib"
|
|
113
|
+
// - Homebrew (Apple Silicon): "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib"
|
|
114
|
+
//
|
|
115
|
+
// To install: brew install sqlite
|
|
116
|
+
// To find path: brew --prefix sqlite
|
|
117
|
+
//
|
|
118
|
+
// "customSqlitePath": "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
|
|
119
|
+
|
|
105
120
|
// ============================================
|
|
106
121
|
// Embedding Model (for similarity search)
|
|
107
122
|
// ============================================
|
|
@@ -205,18 +220,18 @@ const CONFIG_TEMPLATE = `{
|
|
|
205
220
|
|
|
206
221
|
// Days to keep AI session history before cleanup
|
|
207
222
|
"aiSessionRetentionDays": 7,
|
|
208
|
-
|
|
223
|
+
|
|
209
224
|
// ============================================
|
|
210
|
-
// User
|
|
225
|
+
// User Profile System
|
|
211
226
|
// ============================================
|
|
212
|
-
|
|
227
|
+
|
|
213
228
|
// Analyze user prompts every N prompts to build/update your user profile
|
|
214
229
|
// When N uncaptured prompts accumulate, AI will analyze them to identify:
|
|
215
230
|
// - User preferences (code style, communication style, tool preferences)
|
|
216
231
|
// - User patterns (recurring topics, problem domains, technical interests)
|
|
217
232
|
// - User workflows (development habits, sequences, learning style)
|
|
218
233
|
// - Skill level (overall and per-domain assessment)
|
|
219
|
-
"
|
|
234
|
+
"userProfileAnalysisInterval": 10,
|
|
220
235
|
|
|
221
236
|
// Maximum number of preferences to keep in user profile (sorted by confidence)
|
|
222
237
|
// Preferences are things like "prefers code without comments", "likes concise responses"
|
|
@@ -244,13 +259,10 @@ const CONFIG_TEMPLATE = `{
|
|
|
244
259
|
|
|
245
260
|
// Minimum similarity score (0-1) for memory search results
|
|
246
261
|
"similarityThreshold": 0.6,
|
|
247
|
-
|
|
248
|
-
// Maximum number of
|
|
249
|
-
"maxMemories":
|
|
250
|
-
|
|
251
|
-
// Maximum number of project-scoped memories to return in search
|
|
252
|
-
"maxProjectMemories": 10,
|
|
253
|
-
|
|
262
|
+
|
|
263
|
+
// Maximum number of memories to return in search results
|
|
264
|
+
"maxMemories": 10,
|
|
265
|
+
|
|
254
266
|
// ============================================
|
|
255
267
|
// Advanced Settings
|
|
256
268
|
// ============================================
|
|
@@ -297,6 +309,7 @@ function getEmbeddingDimensions(model) {
|
|
|
297
309
|
}
|
|
298
310
|
export const CONFIG = {
|
|
299
311
|
storagePath: expandPath(fileConfig.storagePath ?? DEFAULTS.storagePath),
|
|
312
|
+
customSqlitePath: fileConfig.customSqlitePath ? expandPath(fileConfig.customSqlitePath) : undefined,
|
|
300
313
|
embeddingModel: fileConfig.embeddingModel ?? DEFAULTS.embeddingModel,
|
|
301
314
|
embeddingDimensions: fileConfig.embeddingDimensions ??
|
|
302
315
|
getEmbeddingDimensions(fileConfig.embeddingModel ?? DEFAULTS.embeddingModel),
|
|
@@ -304,7 +317,6 @@ export const CONFIG = {
|
|
|
304
317
|
embeddingApiKey: fileConfig.embeddingApiKey ?? process.env.OPENAI_API_KEY,
|
|
305
318
|
similarityThreshold: fileConfig.similarityThreshold ?? DEFAULTS.similarityThreshold,
|
|
306
319
|
maxMemories: fileConfig.maxMemories ?? DEFAULTS.maxMemories,
|
|
307
|
-
maxProjectMemories: fileConfig.maxProjectMemories ?? DEFAULTS.maxProjectMemories,
|
|
308
320
|
maxProfileItems: fileConfig.maxProfileItems ?? DEFAULTS.maxProfileItems,
|
|
309
321
|
injectProfile: fileConfig.injectProfile ?? DEFAULTS.injectProfile,
|
|
310
322
|
containerTagPrefix: fileConfig.containerTagPrefix ?? DEFAULTS.containerTagPrefix,
|
|
@@ -328,7 +340,7 @@ export const CONFIG = {
|
|
|
328
340
|
autoCleanupRetentionDays: fileConfig.autoCleanupRetentionDays ?? DEFAULTS.autoCleanupRetentionDays,
|
|
329
341
|
deduplicationEnabled: fileConfig.deduplicationEnabled ?? DEFAULTS.deduplicationEnabled,
|
|
330
342
|
deduplicationSimilarityThreshold: fileConfig.deduplicationSimilarityThreshold ?? DEFAULTS.deduplicationSimilarityThreshold,
|
|
331
|
-
|
|
343
|
+
userProfileAnalysisInterval: fileConfig.userProfileAnalysisInterval ?? DEFAULTS.userProfileAnalysisInterval,
|
|
332
344
|
userProfileMaxPreferences: fileConfig.userProfileMaxPreferences ?? DEFAULTS.userProfileMaxPreferences,
|
|
333
345
|
userProfileMaxPatterns: fileConfig.userProfileMaxPatterns ?? DEFAULTS.userProfileMaxPatterns,
|
|
334
346
|
userProfileMaxWorkflows: fileConfig.userProfileMaxWorkflows ?? DEFAULTS.userProfileMaxWorkflows,
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AA0C/D,eAAO,MAAM,iBAAiB,EAAE,MAyhB/B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { formatContextForPrompt } from "./services/context.js";
|
|
|
4
4
|
import { getTags } from "./services/tags.js";
|
|
5
5
|
import { stripPrivateContent, isFullyPrivate } from "./services/privacy.js";
|
|
6
6
|
import { performAutoCapture } from "./services/auto-capture.js";
|
|
7
|
-
import {
|
|
7
|
+
import { performUserProfileLearning } from "./services/user-memory-learning.js";
|
|
8
8
|
import { userPromptManager } from "./services/user-prompt/user-prompt-manager.js";
|
|
9
9
|
import { startWebServer, WebServer } from "./services/web-server.js";
|
|
10
10
|
import { isConfigured, CONFIG } from "./config.js";
|
|
@@ -16,10 +16,11 @@ const MEMORY_NUDGE_MESSAGE = `[MEMORY TRIGGER DETECTED]
|
|
|
16
16
|
The user wants you to remember something. You MUST use the \`memory\` tool with \`mode: "add"\` to save this information.
|
|
17
17
|
|
|
18
18
|
Extract the key information the user wants remembered and save it as a concise, searchable memory.
|
|
19
|
-
- Use \`scope: "project"\` for project-specific
|
|
20
|
-
- Use \`scope: "user"\` for cross-project preferences (e.g., "prefers concise responses")
|
|
19
|
+
- Use \`scope: "project"\` for project-specific knowledge (e.g., "run lint with tests", architecture decisions)
|
|
21
20
|
- Choose an appropriate \`type\`: "preference", "project-config", "learned-pattern", etc.
|
|
22
21
|
|
|
22
|
+
Note: User preferences are automatically learned through the user profile system. Only store project-specific information.
|
|
23
|
+
|
|
23
24
|
DO NOT skip this step. The user explicitly asked you to remember.`;
|
|
24
25
|
function removeCodeBlocks(text) {
|
|
25
26
|
return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "");
|
|
@@ -183,11 +184,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
183
184
|
return;
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
|
-
const
|
|
187
|
-
memoryClient.searchMemories(userMessage, tags.user.tag),
|
|
188
|
-
memoryClient.listMemories(tags.project.tag, CONFIG.maxProjectMemories),
|
|
189
|
-
]);
|
|
190
|
-
const userMemories = userMemoriesResult.success ? userMemoriesResult : { results: [] };
|
|
187
|
+
const projectMemoriesListResult = await memoryClient.listMemories(tags.project.tag, CONFIG.maxMemories);
|
|
191
188
|
const projectMemoriesList = projectMemoriesListResult.success
|
|
192
189
|
? projectMemoriesListResult
|
|
193
190
|
: { memories: [] };
|
|
@@ -203,7 +200,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
203
200
|
timing: 0,
|
|
204
201
|
};
|
|
205
202
|
const userId = tags.user.userEmail || null;
|
|
206
|
-
const memoryContext = formatContextForPrompt(userId,
|
|
203
|
+
const memoryContext = formatContextForPrompt(userId, projectMemories);
|
|
207
204
|
if (memoryContext) {
|
|
208
205
|
const contextPart = {
|
|
209
206
|
id: `memory-context-${Date.now()}`,
|
|
@@ -243,7 +240,6 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
243
240
|
content: tool.schema.string().optional(),
|
|
244
241
|
query: tool.schema.string().optional(),
|
|
245
242
|
type: tool.schema.string().optional(),
|
|
246
|
-
scope: tool.schema.enum(["user", "project"]).optional(),
|
|
247
243
|
memoryId: tool.schema.string().optional(),
|
|
248
244
|
limit: tool.schema.number().optional(),
|
|
249
245
|
},
|
|
@@ -271,28 +267,28 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
271
267
|
commands: [
|
|
272
268
|
{
|
|
273
269
|
command: "add",
|
|
274
|
-
description: "Store a new memory",
|
|
275
|
-
args: ["content", "type?"
|
|
270
|
+
description: "Store a new project memory",
|
|
271
|
+
args: ["content", "type?"],
|
|
276
272
|
},
|
|
277
273
|
{
|
|
278
274
|
command: "search",
|
|
279
|
-
description: "Search memories",
|
|
280
|
-
args: ["query"
|
|
275
|
+
description: "Search project memories",
|
|
276
|
+
args: ["query"],
|
|
281
277
|
},
|
|
282
278
|
{
|
|
283
279
|
command: "profile",
|
|
284
|
-
description: "View user profile",
|
|
285
|
-
args: [
|
|
280
|
+
description: "View user profile (preferences, patterns, workflows)",
|
|
281
|
+
args: [],
|
|
286
282
|
},
|
|
287
283
|
{
|
|
288
284
|
command: "list",
|
|
289
|
-
description: "List recent memories",
|
|
290
|
-
args: ["
|
|
285
|
+
description: "List recent project memories",
|
|
286
|
+
args: ["limit?"],
|
|
291
287
|
},
|
|
292
288
|
{
|
|
293
289
|
command: "forget",
|
|
294
|
-
description: "Remove a memory",
|
|
295
|
-
args: ["memoryId"
|
|
290
|
+
description: "Remove a project memory",
|
|
291
|
+
args: ["memoryId"],
|
|
296
292
|
},
|
|
297
293
|
{
|
|
298
294
|
command: "capture-now",
|
|
@@ -300,10 +296,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
300
296
|
args: [],
|
|
301
297
|
},
|
|
302
298
|
],
|
|
303
|
-
|
|
304
|
-
user: "Cross-project user behaviors, preferences, patterns, requests",
|
|
305
|
-
project: "Project-specific knowledge, decisions, architecture, context",
|
|
306
|
-
},
|
|
299
|
+
note: "User preferences are automatically learned through the user profile system. Only project-specific memories can be added manually.",
|
|
307
300
|
typeGuidance: "Choose appropriate type: preference, architecture, workflow, bug-fix, configuration, pattern, request, context, etc. Be specific and descriptive with categories.",
|
|
308
301
|
});
|
|
309
302
|
}
|
|
@@ -321,8 +314,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
321
314
|
error: "Cannot store fully private content",
|
|
322
315
|
});
|
|
323
316
|
}
|
|
324
|
-
const
|
|
325
|
-
const tagInfo = scope === "user" ? tags.user : tags.project;
|
|
317
|
+
const tagInfo = tags.project;
|
|
326
318
|
const result = await memoryClient.addMemory(sanitizedContent, tagInfo.tag, {
|
|
327
319
|
type: args.type,
|
|
328
320
|
displayName: tagInfo.displayName,
|
|
@@ -340,9 +332,8 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
340
332
|
}
|
|
341
333
|
return JSON.stringify({
|
|
342
334
|
success: true,
|
|
343
|
-
message: `Memory added to
|
|
335
|
+
message: `Memory added to project`,
|
|
344
336
|
id: result.id,
|
|
345
|
-
scope,
|
|
346
337
|
type: args.type,
|
|
347
338
|
});
|
|
348
339
|
}
|
|
@@ -353,58 +344,14 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
353
344
|
error: "query parameter is required for search mode",
|
|
354
345
|
});
|
|
355
346
|
}
|
|
356
|
-
const
|
|
357
|
-
if (
|
|
358
|
-
const result = await memoryClient.searchMemories(args.query, tags.user.tag);
|
|
359
|
-
if (!result.success) {
|
|
360
|
-
return JSON.stringify({
|
|
361
|
-
success: false,
|
|
362
|
-
error: result.error || "Failed to search memories",
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
return formatSearchResults(args.query, scope, result, args.limit);
|
|
366
|
-
}
|
|
367
|
-
if (scope === "project") {
|
|
368
|
-
const result = await memoryClient.searchMemories(args.query, tags.project.tag);
|
|
369
|
-
if (!result.success) {
|
|
370
|
-
return JSON.stringify({
|
|
371
|
-
success: false,
|
|
372
|
-
error: result.error || "Failed to search memories",
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
return formatSearchResults(args.query, scope, result, args.limit);
|
|
376
|
-
}
|
|
377
|
-
const [userResult, projectResult] = await Promise.all([
|
|
378
|
-
memoryClient.searchMemories(args.query, tags.user.tag),
|
|
379
|
-
memoryClient.searchMemories(args.query, tags.project.tag),
|
|
380
|
-
]);
|
|
381
|
-
if (!userResult.success || !projectResult.success) {
|
|
347
|
+
const result = await memoryClient.searchMemories(args.query, tags.project.tag);
|
|
348
|
+
if (!result.success) {
|
|
382
349
|
return JSON.stringify({
|
|
383
350
|
success: false,
|
|
384
|
-
error:
|
|
351
|
+
error: result.error || "Failed to search memories",
|
|
385
352
|
});
|
|
386
353
|
}
|
|
387
|
-
|
|
388
|
-
...(userResult.results || []).map((r) => ({
|
|
389
|
-
...r,
|
|
390
|
-
scope: "user",
|
|
391
|
-
})),
|
|
392
|
-
...(projectResult.results || []).map((r) => ({
|
|
393
|
-
...r,
|
|
394
|
-
scope: "project",
|
|
395
|
-
})),
|
|
396
|
-
].sort((a, b) => b.similarity - a.similarity);
|
|
397
|
-
return JSON.stringify({
|
|
398
|
-
success: true,
|
|
399
|
-
query: args.query,
|
|
400
|
-
count: combined.length,
|
|
401
|
-
results: combined.slice(0, args.limit || 10).map((r) => ({
|
|
402
|
-
id: r.id,
|
|
403
|
-
content: r.memory || r.chunk,
|
|
404
|
-
similarity: Math.round(r.similarity * 100),
|
|
405
|
-
scope: r.scope,
|
|
406
|
-
})),
|
|
407
|
-
});
|
|
354
|
+
return formatSearchResults(args.query, result, args.limit);
|
|
408
355
|
}
|
|
409
356
|
case "profile": {
|
|
410
357
|
const { userProfileManager } = await import("./services/user-profile/user-profile-manager.js");
|
|
@@ -432,10 +379,8 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
432
379
|
});
|
|
433
380
|
}
|
|
434
381
|
case "list": {
|
|
435
|
-
const scope = args.scope || "project";
|
|
436
382
|
const limit = args.limit || 20;
|
|
437
|
-
const
|
|
438
|
-
const result = await memoryClient.listMemories(tagInfo.tag, limit);
|
|
383
|
+
const result = await memoryClient.listMemories(tags.project.tag, limit);
|
|
439
384
|
if (!result.success) {
|
|
440
385
|
return JSON.stringify({
|
|
441
386
|
success: false,
|
|
@@ -445,7 +390,6 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
445
390
|
const memories = result.memories || [];
|
|
446
391
|
return JSON.stringify({
|
|
447
392
|
success: true,
|
|
448
|
-
scope,
|
|
449
393
|
count: memories.length,
|
|
450
394
|
memories: memories.map((m) => ({
|
|
451
395
|
id: m.id,
|
|
@@ -462,7 +406,6 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
462
406
|
error: "memoryId parameter is required for forget mode",
|
|
463
407
|
});
|
|
464
408
|
}
|
|
465
|
-
const scope = args.scope || "project";
|
|
466
409
|
const result = await memoryClient.deleteMemory(args.memoryId);
|
|
467
410
|
if (!result.success) {
|
|
468
411
|
return JSON.stringify({
|
|
@@ -472,7 +415,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
472
415
|
}
|
|
473
416
|
return JSON.stringify({
|
|
474
417
|
success: true,
|
|
475
|
-
message: `Memory ${args.memoryId} removed
|
|
418
|
+
message: `Memory ${args.memoryId} removed`,
|
|
476
419
|
});
|
|
477
420
|
}
|
|
478
421
|
case "capture-now": {
|
|
@@ -509,7 +452,7 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
509
452
|
if (sessionID) {
|
|
510
453
|
await performAutoCapture(ctx, sessionID, directory);
|
|
511
454
|
}
|
|
512
|
-
await
|
|
455
|
+
await performUserProfileLearning(ctx, directory);
|
|
513
456
|
const { cleanupService } = await import("./services/cleanup-service.js");
|
|
514
457
|
const shouldRun = await cleanupService.shouldRunCleanup();
|
|
515
458
|
if (!shouldRun)
|
|
@@ -549,12 +492,11 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
549
492
|
},
|
|
550
493
|
};
|
|
551
494
|
};
|
|
552
|
-
function formatSearchResults(query,
|
|
495
|
+
function formatSearchResults(query, results, limit) {
|
|
553
496
|
const memoryResults = results.results || [];
|
|
554
497
|
return JSON.stringify({
|
|
555
498
|
success: true,
|
|
556
499
|
query,
|
|
557
|
-
scope,
|
|
558
500
|
count: memoryResults.length,
|
|
559
501
|
results: memoryResults.slice(0, limit || 10).map((r) => ({
|
|
560
502
|
id: r.id,
|
|
@@ -8,7 +8,6 @@ interface Memory {
|
|
|
8
8
|
id: string;
|
|
9
9
|
content: string;
|
|
10
10
|
type?: string;
|
|
11
|
-
scope: string;
|
|
12
11
|
createdAt: string;
|
|
13
12
|
updatedAt?: string;
|
|
14
13
|
metadata?: Record<string, unknown>;
|
|
@@ -37,10 +36,9 @@ interface PaginatedResponse<T> {
|
|
|
37
36
|
totalPages: number;
|
|
38
37
|
}
|
|
39
38
|
export declare function handleListTags(): Promise<ApiResponse<{
|
|
40
|
-
user: TagInfo[];
|
|
41
39
|
project: TagInfo[];
|
|
42
40
|
}>>;
|
|
43
|
-
export declare function handleListMemories(tag?: string, page?: number, pageSize?: number,
|
|
41
|
+
export declare function handleListMemories(tag?: string, page?: number, pageSize?: number, includePrompts?: boolean): Promise<ApiResponse<PaginatedResponse<Memory | any>>>;
|
|
44
42
|
export declare function handleAddMemory(data: {
|
|
45
43
|
content: string;
|
|
46
44
|
containerTag: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../src/services/api-handlers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,UAAU,WAAW,CAAC,CAAC,GAAG,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,MAAM;IACd,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,
|
|
1
|
+
{"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../src/services/api-handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,18 +33,16 @@ function safeJSONParse(jsonString) {
|
|
|
33
33
|
function extractScopeFromTag(tag) {
|
|
34
34
|
const parts = tag.split("_");
|
|
35
35
|
if (parts.length >= 3) {
|
|
36
|
-
const scope = parts[1];
|
|
37
36
|
const hash = parts.slice(2).join("_");
|
|
38
|
-
return { scope, hash };
|
|
37
|
+
return { scope: "project", hash };
|
|
39
38
|
}
|
|
40
|
-
return { scope: "
|
|
39
|
+
return { scope: "project", hash: tag };
|
|
41
40
|
}
|
|
42
41
|
export async function handleListTags() {
|
|
43
42
|
try {
|
|
44
43
|
await embeddingService.warmup();
|
|
45
|
-
const userShards = shardManager.getAllShards("user", "");
|
|
46
44
|
const projectShards = shardManager.getAllShards("project", "");
|
|
47
|
-
const allShards = [...
|
|
45
|
+
const allShards = [...projectShards];
|
|
48
46
|
const tagsMap = new Map();
|
|
49
47
|
for (const shard of allShards) {
|
|
50
48
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
@@ -63,19 +61,15 @@ export async function handleListTags() {
|
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
|
-
const userTags = [];
|
|
67
64
|
const projectTags = [];
|
|
68
65
|
for (const tagInfo of tagsMap.values()) {
|
|
69
|
-
if (tagInfo.tag.includes("
|
|
70
|
-
userTags.push(tagInfo);
|
|
71
|
-
}
|
|
72
|
-
else if (tagInfo.tag.includes("_project_")) {
|
|
66
|
+
if (tagInfo.tag.includes("_project_")) {
|
|
73
67
|
projectTags.push(tagInfo);
|
|
74
68
|
}
|
|
75
69
|
}
|
|
76
70
|
return {
|
|
77
71
|
success: true,
|
|
78
|
-
data: {
|
|
72
|
+
data: { project: projectTags },
|
|
79
73
|
};
|
|
80
74
|
}
|
|
81
75
|
catch (error) {
|
|
@@ -83,7 +77,7 @@ export async function handleListTags() {
|
|
|
83
77
|
return { success: false, error: String(error) };
|
|
84
78
|
}
|
|
85
79
|
}
|
|
86
|
-
export async function handleListMemories(tag, page = 1, pageSize = 20,
|
|
80
|
+
export async function handleListMemories(tag, page = 1, pageSize = 20, includePrompts = true) {
|
|
87
81
|
try {
|
|
88
82
|
await embeddingService.warmup();
|
|
89
83
|
let allMemories = [];
|
|
@@ -96,22 +90,12 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, scope, in
|
|
|
96
90
|
allMemories.push(...memories);
|
|
97
91
|
}
|
|
98
92
|
}
|
|
99
|
-
else if (scope) {
|
|
100
|
-
const shards = shardManager.getAllShards(scope, "");
|
|
101
|
-
for (const shard of shards) {
|
|
102
|
-
const db = connectionManager.getConnection(shard.dbPath);
|
|
103
|
-
const memories = vectorSearch.getAllMemories(db);
|
|
104
|
-
allMemories.push(...memories.filter((m) => m.container_tag?.includes(`_${scope}_`)));
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
93
|
else {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const allShards = [...userShards, ...projectShards];
|
|
111
|
-
for (const shard of allShards) {
|
|
94
|
+
const shards = shardManager.getAllShards("project", "");
|
|
95
|
+
for (const shard of shards) {
|
|
112
96
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
113
97
|
const memories = vectorSearch.getAllMemories(db);
|
|
114
|
-
allMemories.push(...memories);
|
|
98
|
+
allMemories.push(...memories.filter((m) => m.container_tag?.includes(`_project_`)));
|
|
115
99
|
}
|
|
116
100
|
}
|
|
117
101
|
const memoriesWithType = allMemories.map((r) => {
|
|
@@ -121,7 +105,6 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, scope, in
|
|
|
121
105
|
id: r.id,
|
|
122
106
|
content: r.content,
|
|
123
107
|
memoryType: r.type,
|
|
124
|
-
scope: r.container_tag?.includes("_user_") ? "user" : "project",
|
|
125
108
|
createdAt: Number(r.created_at),
|
|
126
109
|
updatedAt: r.updated_at ? Number(r.updated_at) : undefined,
|
|
127
110
|
metadata,
|
|
@@ -136,7 +119,7 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, scope, in
|
|
|
136
119
|
};
|
|
137
120
|
});
|
|
138
121
|
let timeline = memoriesWithType;
|
|
139
|
-
if (includePrompts
|
|
122
|
+
if (includePrompts) {
|
|
140
123
|
const prompts = userPromptManager.getCapturedPrompts(tag);
|
|
141
124
|
const promptsWithType = prompts.map((p) => ({
|
|
142
125
|
type: "prompt",
|
|
@@ -194,7 +177,6 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, scope, in
|
|
|
194
177
|
id: item.id,
|
|
195
178
|
content: item.content,
|
|
196
179
|
memoryType: item.memoryType,
|
|
197
|
-
scope: item.scope,
|
|
198
180
|
createdAt: safeToISOString(item.createdAt),
|
|
199
181
|
updatedAt: item.updatedAt ? safeToISOString(item.updatedAt) : undefined,
|
|
200
182
|
metadata: item.metadata,
|
|
@@ -244,12 +226,6 @@ export async function handleAddMemory(data) {
|
|
|
244
226
|
await embeddingService.warmup();
|
|
245
227
|
const vector = await embeddingService.embedWithTimeout(data.content);
|
|
246
228
|
const { scope, hash } = extractScopeFromTag(data.containerTag);
|
|
247
|
-
if (scope === "user") {
|
|
248
|
-
return {
|
|
249
|
-
success: false,
|
|
250
|
-
error: "User-scoped memories are deprecated. Use user profile system instead.",
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
229
|
const shard = shardManager.getWriteShard(scope, hash);
|
|
254
230
|
const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
255
231
|
const now = Date.now();
|
|
@@ -286,9 +262,8 @@ export async function handleDeleteMemory(id, cascade = false) {
|
|
|
286
262
|
if (!id) {
|
|
287
263
|
return { success: false, error: "id is required" };
|
|
288
264
|
}
|
|
289
|
-
const userShards = shardManager.getAllShards("user", "");
|
|
290
265
|
const projectShards = shardManager.getAllShards("project", "");
|
|
291
|
-
const allShards = [...
|
|
266
|
+
const allShards = [...projectShards];
|
|
292
267
|
let deletedPrompt = false;
|
|
293
268
|
for (const shard of allShards) {
|
|
294
269
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
@@ -339,9 +314,8 @@ export async function handleUpdateMemory(id, data) {
|
|
|
339
314
|
return { success: false, error: "id is required" };
|
|
340
315
|
}
|
|
341
316
|
await embeddingService.warmup();
|
|
342
|
-
const userShards = shardManager.getAllShards("user", "");
|
|
343
317
|
const projectShards = shardManager.getAllShards("project", "");
|
|
344
|
-
const allShards = [...
|
|
318
|
+
const allShards = [...projectShards];
|
|
345
319
|
let foundShard = null;
|
|
346
320
|
let existingMemory = null;
|
|
347
321
|
for (const shard of allShards) {
|
|
@@ -408,9 +382,8 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
408
382
|
}
|
|
409
383
|
}
|
|
410
384
|
else {
|
|
411
|
-
const userShards = shardManager.getAllShards("user", "");
|
|
412
385
|
const projectShards = shardManager.getAllShards("project", "");
|
|
413
|
-
const allShards = [...
|
|
386
|
+
const allShards = [...projectShards];
|
|
414
387
|
const uniqueTags = new Set();
|
|
415
388
|
for (const shard of allShards) {
|
|
416
389
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
@@ -444,7 +417,6 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
444
417
|
id: r.id,
|
|
445
418
|
content: r.memory,
|
|
446
419
|
type: r.metadata?.type,
|
|
447
|
-
scope: r.containerTag?.includes("_user_") ? "user" : "project",
|
|
448
420
|
createdAt: safeToISOString(r.metadata?.createdAt),
|
|
449
421
|
updatedAt: r.metadata?.updatedAt ? safeToISOString(r.metadata.updatedAt) : undefined,
|
|
450
422
|
similarity: Math.round(r.similarity * 100),
|
|
@@ -476,9 +448,8 @@ export async function handleSearch(query, tag, page = 1, pageSize = 20) {
|
|
|
476
448
|
export async function handleStats() {
|
|
477
449
|
try {
|
|
478
450
|
await embeddingService.warmup();
|
|
479
|
-
const userShards = shardManager.getAllShards("user", "");
|
|
480
451
|
const projectShards = shardManager.getAllShards("project", "");
|
|
481
|
-
const allShards = [...
|
|
452
|
+
const allShards = [...projectShards];
|
|
482
453
|
let userCount = 0;
|
|
483
454
|
let projectCount = 0;
|
|
484
455
|
const typeCount = {};
|
|
@@ -516,9 +487,8 @@ export async function handlePinMemory(id) {
|
|
|
516
487
|
if (!id) {
|
|
517
488
|
return { success: false, error: "id is required" };
|
|
518
489
|
}
|
|
519
|
-
const userShards = shardManager.getAllShards("user", "");
|
|
520
490
|
const projectShards = shardManager.getAllShards("project", "");
|
|
521
|
-
const allShards = [...
|
|
491
|
+
const allShards = [...projectShards];
|
|
522
492
|
for (const shard of allShards) {
|
|
523
493
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
524
494
|
const memory = vectorSearch.getMemoryById(db, id);
|
|
@@ -539,9 +509,8 @@ export async function handleUnpinMemory(id) {
|
|
|
539
509
|
if (!id) {
|
|
540
510
|
return { success: false, error: "id is required" };
|
|
541
511
|
}
|
|
542
|
-
const userShards = shardManager.getAllShards("user", "");
|
|
543
512
|
const projectShards = shardManager.getAllShards("project", "");
|
|
544
|
-
const allShards = [...
|
|
513
|
+
const allShards = [...projectShards];
|
|
545
514
|
for (const shard of allShards) {
|
|
546
515
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
547
516
|
const memory = vectorSearch.getMemoryById(db, id);
|
|
@@ -6,6 +6,6 @@ interface MemoryResultMinimal {
|
|
|
6
6
|
interface MemoriesResponseMinimal {
|
|
7
7
|
results?: MemoryResultMinimal[];
|
|
8
8
|
}
|
|
9
|
-
export declare function formatContextForPrompt(userId: string | null,
|
|
9
|
+
export declare function formatContextForPrompt(userId: string | null, projectMemories: MemoriesResponseMinimal): string;
|
|
10
10
|
export {};
|
|
11
11
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/services/context.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/services/context.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,eAAe,EAAE,uBAAuB,GACvC,MAAM,CAyBR"}
|
package/dist/services/context.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CONFIG } from "../config.js";
|
|
2
2
|
import { getUserProfileContext } from "./user-profile/profile-context.js";
|
|
3
|
-
export function formatContextForPrompt(userId,
|
|
3
|
+
export function formatContextForPrompt(userId, projectMemories) {
|
|
4
4
|
const parts = ["[MEMORY]"];
|
|
5
5
|
if (CONFIG.injectProfile && userId) {
|
|
6
6
|
const profileContext = getUserProfileContext(userId);
|
|
@@ -17,15 +17,6 @@ export function formatContextForPrompt(userId, userMemories, projectMemories) {
|
|
|
17
17
|
parts.push(`- [${similarity}%] ${content}`);
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
const userResults = userMemories.results || [];
|
|
21
|
-
if (userResults.length > 0) {
|
|
22
|
-
parts.push("\nRelevant Memories:");
|
|
23
|
-
userResults.forEach((mem) => {
|
|
24
|
-
const similarity = Math.round(mem.similarity * 100);
|
|
25
|
-
const content = mem.memory || mem.chunk || "";
|
|
26
|
-
parts.push(`- [${similarity}%] ${content}`);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
20
|
if (parts.length === 1) {
|
|
30
21
|
return "";
|
|
31
22
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
export declare class ConnectionManager {
|
|
3
3
|
private connections;
|
|
4
|
+
private sqliteConfigured;
|
|
5
|
+
private configureSqlite;
|
|
4
6
|
private initDatabase;
|
|
5
7
|
getConnection(dbPath: string): Database;
|
|
6
8
|
closeConnection(dbPath: string): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMtC,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,eAAe;IAyEvB,OAAO,CAAC,YAAY;IAqBpB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAmBvC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASrC,QAAQ,IAAI,IAAI;CAWjB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
|
@@ -2,20 +2,96 @@ import { Database } from "bun:sqlite";
|
|
|
2
2
|
import * as sqliteVec from "sqlite-vec";
|
|
3
3
|
import { existsSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { log } from "../logger.js";
|
|
5
|
+
import { CONFIG } from "../../config.js";
|
|
5
6
|
export class ConnectionManager {
|
|
6
7
|
connections = new Map();
|
|
8
|
+
sqliteConfigured = false;
|
|
9
|
+
configureSqlite() {
|
|
10
|
+
if (this.sqliteConfigured)
|
|
11
|
+
return;
|
|
12
|
+
if (process.platform === "darwin") {
|
|
13
|
+
const customPath = CONFIG.customSqlitePath;
|
|
14
|
+
if (customPath) {
|
|
15
|
+
if (!existsSync(customPath)) {
|
|
16
|
+
throw new Error(`Custom SQLite library not found at: ${customPath}\n` +
|
|
17
|
+
`Please verify the path or install Homebrew SQLite:\n` +
|
|
18
|
+
` brew install sqlite\n` +
|
|
19
|
+
` brew --prefix sqlite`);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
Database.setCustomSQLite(customPath);
|
|
23
|
+
log("Using custom SQLite library", { path: customPath });
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
throw new Error(`Failed to load custom SQLite library: ${error}\n` +
|
|
27
|
+
`Path: ${customPath}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const commonPaths = [
|
|
32
|
+
"/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
|
|
33
|
+
"/usr/local/opt/sqlite/lib/libsqlite3.dylib",
|
|
34
|
+
];
|
|
35
|
+
let foundPath = null;
|
|
36
|
+
for (const path of commonPaths) {
|
|
37
|
+
if (existsSync(path)) {
|
|
38
|
+
foundPath = path;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (foundPath) {
|
|
43
|
+
try {
|
|
44
|
+
Database.setCustomSQLite(foundPath);
|
|
45
|
+
log("Auto-detected and using Homebrew SQLite", { path: foundPath });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new Error(`Failed to load Homebrew SQLite: ${error}\n` +
|
|
49
|
+
`Path: ${foundPath}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
throw new Error(`macOS detected but no compatible SQLite library found.\n\n` +
|
|
54
|
+
`Apple's default SQLite does not support extension loading.\n` +
|
|
55
|
+
`Please install Homebrew SQLite and configure the path:\n\n` +
|
|
56
|
+
`1. Install Homebrew SQLite:\n` +
|
|
57
|
+
` brew install sqlite\n\n` +
|
|
58
|
+
`2. Find the library path:\n` +
|
|
59
|
+
` brew --prefix sqlite\n\n` +
|
|
60
|
+
`3. Add to ~/.config/opencode/opencode-mem.jsonc:\n` +
|
|
61
|
+
` {\n` +
|
|
62
|
+
` "customSqlitePath": "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib"\n` +
|
|
63
|
+
` }\n\n` +
|
|
64
|
+
`Common paths:\n` +
|
|
65
|
+
` - Apple Silicon: /opt/homebrew/opt/sqlite/lib/libsqlite3.dylib\n` +
|
|
66
|
+
` - Intel Mac: /usr/local/opt/sqlite/lib/libsqlite3.dylib`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.sqliteConfigured = true;
|
|
71
|
+
}
|
|
7
72
|
initDatabase(db) {
|
|
8
73
|
db.run("PRAGMA journal_mode = WAL");
|
|
9
74
|
db.run("PRAGMA synchronous = NORMAL");
|
|
10
75
|
db.run("PRAGMA cache_size = -64000");
|
|
11
76
|
db.run("PRAGMA temp_store = MEMORY");
|
|
12
77
|
db.run("PRAGMA foreign_keys = ON");
|
|
13
|
-
|
|
78
|
+
try {
|
|
79
|
+
sqliteVec.load(db);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw new Error(`Failed to load sqlite-vec extension: ${error}\n\n` +
|
|
83
|
+
`This usually means SQLite extension loading is disabled.\n` +
|
|
84
|
+
`On macOS, you must use Homebrew SQLite instead of Apple's SQLite.\n\n` +
|
|
85
|
+
`Solution:\n` +
|
|
86
|
+
`1. Install: brew install sqlite\n` +
|
|
87
|
+
`2. Configure customSqlitePath in ~/.config/opencode/opencode-mem.jsonc`);
|
|
88
|
+
}
|
|
14
89
|
}
|
|
15
90
|
getConnection(dbPath) {
|
|
16
91
|
if (this.connections.has(dbPath)) {
|
|
17
92
|
return this.connections.get(dbPath);
|
|
18
93
|
}
|
|
94
|
+
this.configureSqlite();
|
|
19
95
|
const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
20
96
|
if (!existsSync(dir)) {
|
|
21
97
|
mkdirSync(dir, { recursive: true });
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function performUserProfileLearning(ctx: PluginInput, directory: string): Promise<void>;
|
|
3
3
|
//# sourceMappingURL=user-memory-learning.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-memory-learning.d.ts","sourceRoot":"","sources":["../../src/services/user-memory-learning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AASvD,wBAAsB,
|
|
1
|
+
{"version":3,"file":"user-memory-learning.d.ts","sourceRoot":"","sources":["../../src/services/user-memory-learning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AASvD,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA8Ef"}
|
|
@@ -3,10 +3,10 @@ import { log } from "./logger.js";
|
|
|
3
3
|
import { CONFIG } from "../config.js";
|
|
4
4
|
import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
|
|
5
5
|
import { userProfileManager } from "./user-profile/user-profile-manager.js";
|
|
6
|
-
export async function
|
|
6
|
+
export async function performUserProfileLearning(ctx, directory) {
|
|
7
7
|
try {
|
|
8
8
|
const count = userPromptManager.countUnanalyzedForUserLearning();
|
|
9
|
-
const threshold = CONFIG.
|
|
9
|
+
const threshold = CONFIG.userProfileAnalysisInterval;
|
|
10
10
|
if (count < threshold) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
@@ -30,9 +30,8 @@ async function handleRequest(req) {
|
|
|
30
30
|
const tag = url.searchParams.get("tag") || undefined;
|
|
31
31
|
const page = parseInt(url.searchParams.get("page") || "1");
|
|
32
32
|
const pageSize = parseInt(url.searchParams.get("pageSize") || "20");
|
|
33
|
-
const scope = url.searchParams.get("scope");
|
|
34
33
|
const includePrompts = url.searchParams.get("includePrompts") !== "false";
|
|
35
|
-
const result = await handleListMemories(tag, page, pageSize,
|
|
34
|
+
const result = await handleListMemories(tag, page, pageSize, includePrompts);
|
|
36
35
|
return jsonResponse(result);
|
|
37
36
|
}
|
|
38
37
|
if (path === "/api/memories" && method === "POST") {
|
package/dist/web/app.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
const API_BASE = "";
|
|
2
2
|
|
|
3
3
|
const state = {
|
|
4
|
-
tags: {
|
|
4
|
+
tags: { project: [] },
|
|
5
5
|
memories: [],
|
|
6
6
|
currentPage: 1,
|
|
7
7
|
pageSize: 20,
|
|
8
8
|
totalPages: 1,
|
|
9
9
|
totalItems: 0,
|
|
10
10
|
selectedTag: "",
|
|
11
|
-
currentScope: "project",
|
|
12
11
|
currentView: "project",
|
|
13
12
|
searchQuery: "",
|
|
14
13
|
isSearching: false,
|
|
@@ -55,7 +54,7 @@ function populateTagDropdowns() {
|
|
|
55
54
|
tagFilter.innerHTML = '<option value="">All Tags</option>';
|
|
56
55
|
addTag.innerHTML = '<option value="">Select tag</option>';
|
|
57
56
|
|
|
58
|
-
const scopeTags = state.
|
|
57
|
+
const scopeTags = state.tags.project;
|
|
59
58
|
|
|
60
59
|
scopeTags.forEach((tagInfo) => {
|
|
61
60
|
const displayText = tagInfo.displayName || tagInfo.tag;
|
|
@@ -77,7 +76,7 @@ function populateTagDropdowns() {
|
|
|
77
76
|
async function loadMemories() {
|
|
78
77
|
showRefreshIndicator(true);
|
|
79
78
|
|
|
80
|
-
let endpoint = `/api/memories?page=${state.currentPage}&pageSize=${state.pageSize}&
|
|
79
|
+
let endpoint = `/api/memories?page=${state.currentPage}&pageSize=${state.pageSize}&includePrompts=true`;
|
|
81
80
|
|
|
82
81
|
if (state.isSearching && state.searchQuery) {
|
|
83
82
|
endpoint = `/api/search?q=${encodeURIComponent(state.searchQuery)}&page=${state.currentPage}&pageSize=${state.pageSize}`;
|
|
@@ -173,15 +172,13 @@ function renderMemoryCard(memory) {
|
|
|
173
172
|
: "";
|
|
174
173
|
|
|
175
174
|
let displayInfo = memory.displayName || memory.id;
|
|
176
|
-
if (memory.
|
|
175
|
+
if (memory.projectPath) {
|
|
177
176
|
const pathParts = memory.projectPath.split("/");
|
|
178
177
|
displayInfo = pathParts[pathParts.length - 1] || memory.projectPath;
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
let subtitle = "";
|
|
182
|
-
if (memory.
|
|
183
|
-
subtitle = `<span class="memory-subtitle">${escapeHtml(memory.userEmail)}</span>`;
|
|
184
|
-
} else if (memory.scope === "project" && memory.projectPath) {
|
|
181
|
+
if (memory.projectPath) {
|
|
185
182
|
subtitle = `<span class="memory-subtitle">${escapeHtml(memory.projectPath)}</span>`;
|
|
186
183
|
}
|
|
187
184
|
|
|
@@ -202,7 +199,6 @@ function renderMemoryCard(memory) {
|
|
|
202
199
|
<div class="memory-header">
|
|
203
200
|
<div class="meta">
|
|
204
201
|
<input type="checkbox" class="memory-checkbox" data-id="${memory.id}" ${isSelected ? "checked" : ""} />
|
|
205
|
-
<span class="badge badge-${memory.scope}">${memory.scope}</span>
|
|
206
202
|
${memory.memoryType ? `<span class="badge badge-type">${memory.memoryType}</span>` : ""}
|
|
207
203
|
${isLinked ? '<span class="badge badge-linked"><i data-lucide="link" class="icon-sm"></i> LINKED</span>' : ""}
|
|
208
204
|
${similarityHtml}
|
|
@@ -280,10 +276,9 @@ function updatePagination() {
|
|
|
280
276
|
}
|
|
281
277
|
|
|
282
278
|
function updateSectionTitle() {
|
|
283
|
-
const scopeName = state.currentScope.toUpperCase();
|
|
284
279
|
const title = state.isSearching
|
|
285
280
|
? `└─ SEARCH RESULTS (${state.totalItems}) ──`
|
|
286
|
-
: `└─
|
|
281
|
+
: `└─ PROJECT MEMORIES (${state.totalItems}) ──`;
|
|
287
282
|
document.getElementById("section-title").textContent = title;
|
|
288
283
|
}
|
|
289
284
|
|
|
@@ -291,9 +286,6 @@ async function loadStats() {
|
|
|
291
286
|
const result = await fetchAPI("/api/stats");
|
|
292
287
|
if (result.success) {
|
|
293
288
|
document.getElementById("stats-total").textContent = `Total: ${result.data.total}`;
|
|
294
|
-
document.getElementById("stats-user").textContent = `User: ${result.data.byScope.user}`;
|
|
295
|
-
document.getElementById("stats-project").textContent =
|
|
296
|
-
`Project: ${result.data.byScope.project}`;
|
|
297
289
|
}
|
|
298
290
|
}
|
|
299
291
|
|
|
@@ -495,13 +487,10 @@ function changePage(delta) {
|
|
|
495
487
|
}
|
|
496
488
|
|
|
497
489
|
function handleAddScopeChange() {
|
|
498
|
-
const scope = document.getElementById("add-scope").value;
|
|
499
490
|
const tagDropdown = document.getElementById("add-tag");
|
|
500
491
|
|
|
501
492
|
tagDropdown.innerHTML = '<option value="">Select tag</option>';
|
|
502
493
|
|
|
503
|
-
if (!scope || scope !== "project") return;
|
|
504
|
-
|
|
505
494
|
const tags = state.tags.project;
|
|
506
495
|
tags.forEach((tagInfo) => {
|
|
507
496
|
const displayText = tagInfo.displayName || tagInfo.tag;
|
|
@@ -952,8 +941,6 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
952
941
|
loadMemories();
|
|
953
942
|
});
|
|
954
943
|
|
|
955
|
-
document.getElementById("add-scope").addEventListener("change", handleAddScopeChange);
|
|
956
|
-
|
|
957
944
|
document.getElementById("search-btn").addEventListener("click", performSearch);
|
|
958
945
|
document.getElementById("clear-search-btn").addEventListener("click", clearSearch);
|
|
959
946
|
document.getElementById("search-input").addEventListener("keypress", (e) => {
|
package/dist/web/index.html
CHANGED
|
@@ -27,8 +27,6 @@
|
|
|
27
27
|
</div>
|
|
28
28
|
<div class="stats-bar">
|
|
29
29
|
<span id="stats-total">Total: 0</span>
|
|
30
|
-
<span id="stats-user">User: 0</span>
|
|
31
|
-
<span id="stats-project">Project: 0</span>
|
|
32
30
|
<span id="refresh-indicator" class="hidden"
|
|
33
31
|
><i data-lucide="rotate-cw" class="icon icon-spin"></i
|
|
34
32
|
></span>
|
|
@@ -145,14 +143,6 @@
|
|
|
145
143
|
<h2>└─ ADD NEW MEMORY ──</h2>
|
|
146
144
|
<form id="add-form">
|
|
147
145
|
<div class="form-row">
|
|
148
|
-
<div class="form-group">
|
|
149
|
-
<label>Scope:</label>
|
|
150
|
-
<select id="add-scope" required>
|
|
151
|
-
<option value="">Select scope</option>
|
|
152
|
-
<option value="project">Project</option>
|
|
153
|
-
</select>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
146
|
<div class="form-group">
|
|
157
147
|
<label>Tag:</label>
|
|
158
148
|
<select id="add-tag" required>
|