opencode-mem 2.1.1 → 2.2.0

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 CHANGED
@@ -14,7 +14,7 @@ OpenCode Memory provides AI coding agents with the ability to remember and recal
14
14
  - **Dual Memory Scopes**: Separate user-level and project-level memory contexts
15
15
  - **Unified Timeline**: Browse memories and prompts together with linking support
16
16
  - **Prompt-Memory Linking**: Bidirectional links between prompts and generated memories
17
- - **User Learning System**: Analyzes user patterns and preferences from conversation history
17
+ - **User Profile System**: Structured learning with preferences, patterns, workflows, and skill assessment
18
18
  - **Web Interface**: Full-featured UI for memory management and search
19
19
  - **Auto-Capture System**: Intelligent prompt-based memory extraction
20
20
  - **Multi-Provider AI**: Support for OpenAI, Anthropic, and OpenAI-compatible APIs
@@ -52,15 +52,25 @@ bun run build
52
52
  ### Basic Usage
53
53
 
54
54
  ```typescript
55
- memory({ mode: "add", content: "User prefers TypeScript", scope: "user" })
56
- memory({ mode: "search", query: "coding preferences", scope: "user" })
55
+ memory({ mode: "add", content: "Project uses microservices", scope: "project" })
56
+ memory({ mode: "search", query: "architecture decisions", scope: "project" })
57
57
  memory({ mode: "profile" })
58
58
  ```
59
59
 
60
+ **Note**: User-scoped `add` is deprecated in v2.2+. Use profile system instead.
61
+
60
62
  ### Web Interface
61
63
 
62
64
  Access at `http://127.0.0.1:4747` to browse memories, view prompt-memory links, and manage your memory database.
63
65
 
66
+ **Project Memory Timeline:**
67
+
68
+ ![Project Memory Timeline](.github/screenshot-project-memory.png)
69
+
70
+ **User Profile Viewer:**
71
+
72
+ ![User Profile Viewer](.github/screenshot-user-profile.png)
73
+
64
74
  ### Configuration
65
75
 
66
76
  Configuration file: `~/.config/opencode/opencode-mem.jsonc`
@@ -75,10 +85,28 @@ Configuration file: `~/.config/opencode/opencode-mem.jsonc`
75
85
  "memoryProvider": "openai-chat",
76
86
  "memoryModel": "gpt-4",
77
87
  "memoryApiUrl": "https://api.openai.com/v1",
78
- "memoryApiKey": "sk-..."
88
+ "memoryApiKey": "sk-...",
89
+ "userMemoryAnalysisInterval": 10,
90
+ "userProfileMaxPreferences": 20,
91
+ "userProfileMaxPatterns": 15,
92
+ "userProfileMaxWorkflows": 10,
93
+ "userProfileConfidenceDecayDays": 30,
94
+ "userProfileChangelogRetentionCount": 5
79
95
  }
80
96
  ```
81
97
 
98
+ ## Breaking Changes (v2.2)
99
+
100
+ **User-scoped memories deprecated in favor of structured user profiles:**
101
+
102
+ - Removed: User-scoped `addMemory` (now returns error)
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
107
+
108
+ **Migration required**: Update code using `mode: "profile"` to handle new structure.
109
+
82
110
  ## Breaking Changes (v2.0)
83
111
 
84
112
  **Token-based auto-capture has been replaced with prompt-based system:**
@@ -98,6 +126,7 @@ For detailed documentation, see the [Wiki](https://github.com/tickernelz/opencod
98
126
  - [Installation Guide](https://github.com/tickernelz/opencode-mem/wiki/Installation-Guide)
99
127
  - [Quick Start](https://github.com/tickernelz/opencode-mem/wiki/Quick-Start)
100
128
  - [Configuration Guide](https://github.com/tickernelz/opencode-mem/wiki/Configuration-Guide)
129
+ - [User Profile System](https://github.com/tickernelz/opencode-mem/wiki/User-Profile)
101
130
  - [Memory Operations](https://github.com/tickernelz/opencode-mem/wiki/Memory-Operations)
102
131
  - [Auto-Capture System](https://github.com/tickernelz/opencode-mem/wiki/Auto-Capture-System)
103
132
  - [Web Interface](https://github.com/tickernelz/opencode-mem/wiki/Web-Interface)
@@ -121,18 +150,21 @@ Automatically extracts memories from conversations:
121
150
  3. Links memory to source prompt
122
151
  4. Skips non-technical conversations
123
152
 
124
- ### User Learning System
153
+ ### User Profile System
154
+
155
+ Builds structured user profile from conversation history (default: every 10 prompts):
125
156
 
126
- Analyzes batches of prompts to identify patterns (default: every 10 prompts):
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
127
161
 
128
- - Coding style preferences
129
- - Communication patterns
130
- - Tool preferences
131
- - Skill level indicators
162
+ Profile includes versioning, changelog, and confidence decay mechanism.
132
163
 
133
164
  ### Web Interface
134
165
 
135
166
  - Unified timeline of memories and prompts
167
+ - User profile viewer with changelog
136
168
  - Visual prompt-memory link indicators
137
169
  - Cascade delete for linked items
138
170
  - Bulk operations
@@ -144,7 +176,7 @@ Analyzes batches of prompts to identify patterns (default: every 10 prompts):
144
176
  ### Memory Tool
145
177
 
146
178
  ```typescript
147
- memory({ mode: "add", content: "...", scope: "user|project" })
179
+ memory({ mode: "add", content: "...", scope: "project" })
148
180
  memory({ mode: "search", query: "...", scope: "user|project" })
149
181
  memory({ mode: "list", scope: "user|project", limit: 10 })
150
182
  memory({ mode: "profile" })
@@ -154,14 +186,25 @@ memory({ mode: "auto-capture-stats" })
154
186
  memory({ mode: "capture-now" })
155
187
  ```
156
188
 
189
+ **Note**: `scope: "user"` for `add` mode is deprecated in v2.2+.
190
+
157
191
  ### REST API
158
192
 
193
+ **Memory & Prompt Management:**
159
194
  - `GET /api/memories?scope=project&includePrompts=true` - List memories/prompts
160
195
  - `POST /api/memories` - Create memory
161
196
  - `PUT /api/memories/:id` - Update memory
162
197
  - `DELETE /api/memories/:id?cascade=true` - Delete memory (and linked prompt)
163
198
  - `DELETE /api/prompts/:id?cascade=true` - Delete prompt (and linked memory)
164
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:**
165
208
  - `POST /api/cleanup` - Run cleanup
166
209
  - `POST /api/deduplicate` - Run deduplication
167
210
 
@@ -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;AAyC/D,eAAO,MAAM,iBAAiB,EAAE,MAwlB/B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAyC/D,eAAO,MAAM,iBAAiB,EAAE,MAulB/B,CAAC"}
package/dist/index.js CHANGED
@@ -109,7 +109,6 @@ export const OpenCodeMemPlugin = async (ctx) => {
109
109
  "chat.message": async (input, output) => {
110
110
  if (!isConfigured())
111
111
  return;
112
- const start = Date.now();
113
112
  try {
114
113
  const textParts = output.parts.filter((p) => p.type === "text");
115
114
  if (textParts.length === 0)
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";AAOA,QAAA,MAAQ,iBAAiB,sCAA+B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";AACA,QAAA,MAAQ,iBAAiB,sCAA+B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,eAAe,iBAAiB,CAAC"}
package/dist/plugin.js CHANGED
@@ -1,8 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { fileURLToPath } from "node:url";
3
- import { dirname } from "node:path";
4
- const __filename = fileURLToPath(import.meta.url);
5
- const __dirname = dirname(__filename);
6
2
  const { OpenCodeMemPlugin } = await import("./index.js");
7
3
  export { OpenCodeMemPlugin };
8
4
  export default OpenCodeMemPlugin;
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AA2BvF,qBAAa,yBAA0B,SAAQ,cAAc;IAC3D,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAIpB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IAmJ1B,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,gBAAgB;CA+BzB"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AA2BvF,qBAAa,yBAA0B,SAAQ,cAAc;IAC3D,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAIpB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IAmJ1B,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,gBAAgB;CAsBzB"}
@@ -153,24 +153,18 @@ export class AnthropicMessagesProvider extends BaseAIProvider {
153
153
  if (!data || typeof data !== "object") {
154
154
  throw new Error("Response is not an object");
155
155
  }
156
- if (data.memories && Array.isArray(data.memories)) {
157
- const validMemories = data.memories.filter((m) => {
158
- return (m &&
159
- typeof m === "object" &&
160
- typeof m.summary === "string" &&
161
- m.summary.trim().length > 0 &&
162
- (m.scope === "user" || m.scope === "project") &&
163
- typeof m.type === "string" &&
164
- m.type.trim().length > 0);
165
- });
166
- if (validMemories.length === 0) {
167
- throw new Error("No valid memories in response");
168
- }
169
- return { memories: validMemories };
156
+ if (Array.isArray(data)) {
157
+ throw new Error("Response cannot be an array");
170
158
  }
171
- if (data.summary && typeof data.summary === "string" && data.summary.trim().length > 0) {
172
- return data;
159
+ const keys = Object.keys(data);
160
+ if (keys.length === 0) {
161
+ throw new Error("Response object is empty");
162
+ }
163
+ for (const key of keys) {
164
+ if (data[key] === undefined || data[key] === null) {
165
+ throw new Error(`Response field '${key}' is null or undefined`);
166
+ }
173
167
  }
174
- throw new Error("Invalid response format: missing summary or memories field");
168
+ return data;
175
169
  }
176
170
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openai-chat-completion.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/openai-chat-completion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAkBlE,qBAAa,4BAA6B,SAAQ,cAAc;IAC9D,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAIpB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IA2K1B,OAAO,CAAC,gBAAgB;CA+BzB"}
1
+ {"version":3,"file":"openai-chat-completion.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/openai-chat-completion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAkBlE,qBAAa,4BAA6B,SAAQ,cAAc;IAC9D,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAIpB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IA2K1B,OAAO,CAAC,gBAAgB;CAsBzB"}
@@ -158,24 +158,18 @@ export class OpenAIChatCompletionProvider extends BaseAIProvider {
158
158
  if (!data || typeof data !== "object") {
159
159
  throw new Error("Response is not an object");
160
160
  }
161
- if (data.memories && Array.isArray(data.memories)) {
162
- const validMemories = data.memories.filter((m) => {
163
- return (m &&
164
- typeof m === "object" &&
165
- typeof m.summary === "string" &&
166
- m.summary.trim().length > 0 &&
167
- (m.scope === "user" || m.scope === "project") &&
168
- typeof m.type === "string" &&
169
- m.type.trim().length > 0);
170
- });
171
- if (validMemories.length === 0) {
172
- throw new Error("No valid memories in response");
173
- }
174
- return { memories: validMemories };
161
+ if (Array.isArray(data)) {
162
+ throw new Error("Response cannot be an array");
175
163
  }
176
- if (data.summary && typeof data.summary === "string" && data.summary.trim().length > 0) {
177
- return data;
164
+ const keys = Object.keys(data);
165
+ if (keys.length === 0) {
166
+ throw new Error("Response object is empty");
167
+ }
168
+ for (const key of keys) {
169
+ if (data[key] === undefined || data[key] === null) {
170
+ throw new Error(`Response field '${key}' is null or undefined`);
171
+ }
178
172
  }
179
- throw new Error("Invalid response format: missing summary or memories field");
173
+ return data;
180
174
  }
181
175
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openai-responses.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/openai-responses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAsBvF,qBAAa,uBAAwB,SAAQ,cAAc;IACzD,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAIpB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IAuH1B,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,gBAAgB;CA+BzB"}
1
+ {"version":3,"file":"openai-responses.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/openai-responses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAsBvF,qBAAa,uBAAwB,SAAQ,cAAc;IACzD,OAAO,CAAC,gBAAgB,CAAmB;gBAE/B,MAAM,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB;IAK3D,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAIpB,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IAuH1B,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,gBAAgB;CAsBzB"}
@@ -168,24 +168,18 @@ export class OpenAIResponsesProvider extends BaseAIProvider {
168
168
  if (!data || typeof data !== "object") {
169
169
  throw new Error("Response is not an object");
170
170
  }
171
- if (data.memories && Array.isArray(data.memories)) {
172
- const validMemories = data.memories.filter((m) => {
173
- return (m &&
174
- typeof m === "object" &&
175
- typeof m.summary === "string" &&
176
- m.summary.trim().length > 0 &&
177
- (m.scope === "user" || m.scope === "project") &&
178
- typeof m.type === "string" &&
179
- m.type.trim().length > 0);
180
- });
181
- if (validMemories.length === 0) {
182
- throw new Error("No valid memories in response");
183
- }
184
- return { memories: validMemories };
171
+ if (Array.isArray(data)) {
172
+ throw new Error("Response cannot be an array");
185
173
  }
186
- if (data.summary && typeof data.summary === "string" && data.summary.trim().length > 0) {
187
- return data;
174
+ const keys = Object.keys(data);
175
+ if (keys.length === 0) {
176
+ throw new Error("Response object is empty");
177
+ }
178
+ for (const key of keys) {
179
+ if (data[key] === undefined || data[key] === null) {
180
+ throw new Error(`Response field '${key}' is null or undefined`);
181
+ }
188
182
  }
189
- throw new Error("Invalid response format: missing summary or memories field");
183
+ return data;
190
184
  }
191
185
  }
@@ -106,5 +106,9 @@ export declare function handleDeletePrompt(id: string, cascade?: boolean): Promi
106
106
  export declare function handleBulkDeletePrompts(ids: string[], cascade?: boolean): Promise<ApiResponse<{
107
107
  deleted: number;
108
108
  }>>;
109
+ export declare function handleGetUserProfile(userId?: string): Promise<ApiResponse<any>>;
110
+ export declare function handleGetProfileChangelog(profileId: string, limit?: number): Promise<ApiResponse<any[]>>;
111
+ export declare function handleGetProfileSnapshot(changelogId: string): Promise<ApiResponse<any>>;
112
+ export declare function handleRefreshProfile(userId?: string): Promise<ApiResponse<any>>;
109
113
  export {};
110
114
  //# sourceMappingURL=api-handlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../src/services/api-handlers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../src/services/api-handlers.ts"],"names":[],"mappings":"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"}
@@ -149,7 +149,40 @@ export async function handleListMemories(tag, page = 1, pageSize = 20, scope, in
149
149
  }));
150
150
  timeline = [...memoriesWithType, ...promptsWithType];
151
151
  }
152
- timeline.sort((a, b) => b.createdAt - a.createdAt);
152
+ const linkedPairs = new Map();
153
+ const standalone = [];
154
+ for (const item of timeline) {
155
+ if (item.type === "memory" && item.linkedPromptId) {
156
+ if (!linkedPairs.has(item.linkedPromptId)) {
157
+ linkedPairs.set(item.linkedPromptId, { memory: item, prompt: null });
158
+ }
159
+ else {
160
+ linkedPairs.get(item.linkedPromptId).memory = item;
161
+ }
162
+ }
163
+ else if (item.type === "prompt" && item.linkedMemoryId) {
164
+ if (!linkedPairs.has(item.id)) {
165
+ linkedPairs.set(item.id, { memory: null, prompt: item });
166
+ }
167
+ else {
168
+ linkedPairs.get(item.id).prompt = item;
169
+ }
170
+ }
171
+ else {
172
+ standalone.push(item);
173
+ }
174
+ }
175
+ const sortedTimeline = [];
176
+ const pairs = Array.from(linkedPairs.values())
177
+ .filter((p) => p.memory && p.prompt)
178
+ .sort((a, b) => b.memory.createdAt - a.memory.createdAt);
179
+ for (const pair of pairs) {
180
+ sortedTimeline.push(pair.memory);
181
+ sortedTimeline.push(pair.prompt);
182
+ }
183
+ standalone.sort((a, b) => b.createdAt - a.createdAt);
184
+ sortedTimeline.push(...standalone);
185
+ timeline = sortedTimeline;
153
186
  const total = timeline.length;
154
187
  const totalPages = Math.ceil(total / pageSize);
155
188
  const offset = (page - 1) * pageSize;
@@ -211,6 +244,12 @@ export async function handleAddMemory(data) {
211
244
  await embeddingService.warmup();
212
245
  const vector = await embeddingService.embedWithTimeout(data.content);
213
246
  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
+ }
214
253
  const shard = shardManager.getWriteShard(scope, hash);
215
254
  const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
216
255
  const now = Date.now();
@@ -605,3 +644,118 @@ export async function handleBulkDeletePrompts(ids, cascade = false) {
605
644
  return { success: false, error: String(error) };
606
645
  }
607
646
  }
647
+ export async function handleGetUserProfile(userId) {
648
+ try {
649
+ const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
650
+ const { getTags } = await import("./tags.js");
651
+ let targetUserId = userId;
652
+ if (!targetUserId) {
653
+ const tags = getTags(process.cwd());
654
+ targetUserId = tags.user.userEmail || "unknown";
655
+ }
656
+ const profile = userProfileManager.getActiveProfile(targetUserId);
657
+ if (!profile) {
658
+ return {
659
+ success: true,
660
+ data: {
661
+ exists: false,
662
+ userId: targetUserId,
663
+ message: "No profile found. Keep chatting to build your profile.",
664
+ },
665
+ };
666
+ }
667
+ const profileData = JSON.parse(profile.profileData);
668
+ return {
669
+ success: true,
670
+ data: {
671
+ exists: true,
672
+ id: profile.id,
673
+ userId: profile.userId,
674
+ displayName: profile.displayName,
675
+ userName: profile.userName,
676
+ userEmail: profile.userEmail,
677
+ version: profile.version,
678
+ createdAt: safeToISOString(profile.createdAt),
679
+ lastAnalyzedAt: safeToISOString(profile.lastAnalyzedAt),
680
+ totalPromptsAnalyzed: profile.totalPromptsAnalyzed,
681
+ profileData,
682
+ },
683
+ };
684
+ }
685
+ catch (error) {
686
+ log("handleGetUserProfile: error", { error: String(error) });
687
+ return { success: false, error: String(error) };
688
+ }
689
+ }
690
+ export async function handleGetProfileChangelog(profileId, limit = 5) {
691
+ try {
692
+ if (!profileId) {
693
+ return { success: false, error: "profileId is required" };
694
+ }
695
+ const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
696
+ const changelogs = userProfileManager.getProfileChangelogs(profileId, limit);
697
+ const formattedChangelogs = changelogs.map((c) => ({
698
+ id: c.id,
699
+ profileId: c.profileId,
700
+ version: c.version,
701
+ changeType: c.changeType,
702
+ changeSummary: c.changeSummary,
703
+ createdAt: safeToISOString(c.createdAt),
704
+ }));
705
+ return { success: true, data: formattedChangelogs };
706
+ }
707
+ catch (error) {
708
+ log("handleGetProfileChangelog: error", { error: String(error) });
709
+ return { success: false, error: String(error) };
710
+ }
711
+ }
712
+ export async function handleGetProfileSnapshot(changelogId) {
713
+ try {
714
+ if (!changelogId) {
715
+ return { success: false, error: "changelogId is required" };
716
+ }
717
+ const { userProfileManager } = await import("./user-profile/user-profile-manager.js");
718
+ const changelogs = userProfileManager.getProfileChangelogs("", 1000);
719
+ const changelog = changelogs.find((c) => c.id === changelogId);
720
+ if (!changelog) {
721
+ return { success: false, error: "Changelog not found" };
722
+ }
723
+ const profileData = JSON.parse(changelog.profileDataSnapshot);
724
+ return {
725
+ success: true,
726
+ data: {
727
+ version: changelog.version,
728
+ createdAt: safeToISOString(changelog.createdAt),
729
+ profileData,
730
+ },
731
+ };
732
+ }
733
+ catch (error) {
734
+ log("handleGetProfileSnapshot: error", { error: String(error) });
735
+ return { success: false, error: String(error) };
736
+ }
737
+ }
738
+ export async function handleRefreshProfile(userId) {
739
+ try {
740
+ const { getTags } = await import("./tags.js");
741
+ const { userPromptManager } = await import("./user-prompt/user-prompt-manager.js");
742
+ let targetUserId = userId;
743
+ if (!targetUserId) {
744
+ const tags = getTags(process.cwd());
745
+ targetUserId = tags.user.userEmail || "unknown";
746
+ }
747
+ const unanalyzedCount = userPromptManager.countUnanalyzedForUserLearning();
748
+ return {
749
+ success: true,
750
+ data: {
751
+ message: "Profile refresh queued",
752
+ unanalyzedPrompts: unanalyzedCount,
753
+ note: "Profile will be updated when threshold is reached",
754
+ },
755
+ };
756
+ }
757
+ catch (error) {
758
+ log("handleRefreshProfile: error", { error: String(error) });
759
+ return { success: false, error: String(error) };
760
+ }
761
+ }
@@ -37,7 +37,7 @@ export async function performAutoCapture(ctx, sessionID, directory) {
37
37
  const tags = getTags(directory);
38
38
  const latestMemory = await getLatestProjectMemory(tags.project.tag);
39
39
  const context = buildMarkdownContext(prompt.content, textResponses, toolCalls, latestMemory);
40
- const summaryResult = await generateSummary(ctx, context, sessionID);
40
+ const summaryResult = await generateSummary(context, sessionID);
41
41
  if (!summaryResult || summaryResult.type === "skip") {
42
42
  log("Auto-capture: skipped non-technical conversation", { sessionID });
43
43
  userPromptManager.deletePrompt(prompt.id);
@@ -179,7 +179,7 @@ function buildMarkdownContext(userPrompt, textResponses, toolCalls, latestMemory
179
179
  }
180
180
  return sections.join("\n");
181
181
  }
182
- async function generateSummary(ctx, context, sessionID) {
182
+ async function generateSummary(context, sessionID) {
183
183
  if (!CONFIG.memoryModel || !CONFIG.memoryApiUrl || !CONFIG.memoryApiKey) {
184
184
  throw new Error("External API not configured for auto-capture");
185
185
  }
@@ -1 +1 @@
1
- {"version":3,"file":"deduplication-service.d.ts","sourceRoot":"","sources":["../../src/services/deduplication-service.ts"],"names":[],"mappings":"AAOA,UAAU,cAAc;IACtB,cAAc,EAAE;QACd,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,EAAE,KAAK,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,UAAU,mBAAmB;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,cAAc,EAAE,CAAC;CACvC;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,SAAS,CAAkB;IAE7B,yBAAyB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAgH/D,OAAO,CAAC,gBAAgB;IAoBxB,SAAS;;;;;CAOV;AAED,eAAO,MAAM,oBAAoB,sBAA6B,CAAC"}
1
+ {"version":3,"file":"deduplication-service.d.ts","sourceRoot":"","sources":["../../src/services/deduplication-service.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,cAAc,EAAE;QACd,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,UAAU,EAAE,KAAK,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,UAAU,mBAAmB;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,cAAc,EAAE,CAAC;CACvC;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,SAAS,CAAkB;IAE7B,yBAAyB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IA+G/D,OAAO,CAAC,gBAAgB;IAoBxB,SAAS;;;;;CAOV;AAED,eAAO,MAAM,oBAAoB,sBAA6B,CAAC"}
@@ -1,4 +1,3 @@
1
- import { embeddingService } from "./embedding.js";
2
1
  import { shardManager } from "./sqlite/shard-manager.js";
3
2
  import { vectorSearch } from "./sqlite/vector-search.js";
4
3
  import { connectionManager } from "./sqlite/connection-manager.js";
@@ -34,10 +33,9 @@ export class DeduplicationService {
34
33
  }
35
34
  contentMap.get(key).push(memory);
36
35
  }
37
- for (const [key, duplicates] of contentMap) {
36
+ for (const [, duplicates] of contentMap) {
38
37
  if (duplicates.length > 1) {
39
38
  duplicates.sort((a, b) => Number(b.created_at) - Number(a.created_at));
40
- const keep = duplicates[0];
41
39
  const toDelete = duplicates.slice(1);
42
40
  for (const dup of toDelete) {
43
41
  try {
@@ -1,6 +1,6 @@
1
1
  import { appendFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2
2
  import { homedir } from "os";
3
- import { join, dirname } from "path";
3
+ import { join } from "path";
4
4
  const LOG_DIR = join(homedir(), ".opencode-mem");
5
5
  const LOG_FILE = join(LOG_DIR, "opencode-mem.log");
6
6
  if (!existsSync(LOG_DIR)) {
@@ -135,7 +135,9 @@ Use the update_user_profile tool to save the ${existingProfile ? "updated" : "ne
135
135
  type: "function",
136
136
  function: {
137
137
  name: "update_user_profile",
138
- description: existingProfile ? "Update existing user profile with new insights" : "Create new user profile",
138
+ description: existingProfile
139
+ ? "Update existing user profile with new insights"
140
+ : "Create new user profile",
139
141
  parameters: {
140
142
  type: "object",
141
143
  properties: {
@@ -1 +1 @@
1
- {"version":3,"file":"user-profile-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/user-profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,WAAW,EACX,oBAAoB,EACpB,eAAe,EAChB,MAAM,YAAY,CAAC;AAIpB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IA0CpB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAapD,aAAa,CACX,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,eAAe,EAAE,MAAM,GACtB,MAAM;IA8BT,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,yBAAyB,EAAE,MAAM,EACjC,aAAa,EAAE,MAAM,GACpB,IAAI;IAuBP,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,oBAAoB;IAiB5B,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,oBAAoB,EAAE;IAYnF,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA2B7C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKtC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAOrD,oBAAoB,IAAI,WAAW,EAAE;IAMrC,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;IAYtB,gBAAgB,CACd,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAChC,eAAe;CAyFnB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
1
+ {"version":3,"file":"user-profile-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/user-profile-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIrF,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IA0CpB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAapD,aAAa,CACX,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,eAAe,EAAE,MAAM,GACtB,MAAM;IA8BT,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,EAC5B,yBAAyB,EAAE,MAAM,EACjC,aAAa,EAAE,MAAM,GACpB,IAAI;IA6BP,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,oBAAoB;IAiB5B,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,oBAAoB,EAAE;IAYnF,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA2B7C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKtC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAOrD,oBAAoB,IAAI,WAAW,EAAE;IAMrC,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,cAAc;IAYtB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe;CAyFhG;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join, dirname } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { handleListTags, handleListMemories, handleAddMemory, handleDeleteMemory, handleBulkDelete, handleUpdateMemory, handleSearch, handleStats, handlePinMemory, handleUnpinMemory, handleRunCleanup, handleRunDeduplication, handleDetectMigration, handleRunMigration, handleDeletePrompt, handleBulkDeletePrompts, } from "./api-handlers.js";
4
+ import { handleListTags, handleListMemories, handleAddMemory, handleDeleteMemory, handleBulkDelete, handleUpdateMemory, handleSearch, handleStats, handlePinMemory, handleUnpinMemory, handleRunCleanup, handleRunDeduplication, handleDetectMigration, handleRunMigration, handleDeletePrompt, handleBulkDeletePrompts, handleGetUserProfile, handleGetProfileChangelog, handleGetProfileSnapshot, handleRefreshProfile, } from "./api-handlers.js";
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = dirname(__filename);
7
7
  let server = null;
@@ -133,6 +133,34 @@ async function handleRequest(req) {
133
133
  const result = await handleBulkDeletePrompts(body.ids || [], cascade);
134
134
  return jsonResponse(result);
135
135
  }
136
+ if (path === "/api/user-profile" && method === "GET") {
137
+ const userId = url.searchParams.get("userId") || undefined;
138
+ const result = await handleGetUserProfile(userId);
139
+ return jsonResponse(result);
140
+ }
141
+ if (path === "/api/user-profile/changelog" && method === "GET") {
142
+ const profileId = url.searchParams.get("profileId");
143
+ const limit = parseInt(url.searchParams.get("limit") || "5");
144
+ if (!profileId) {
145
+ return jsonResponse({ success: false, error: "profileId parameter required" });
146
+ }
147
+ const result = await handleGetProfileChangelog(profileId, limit);
148
+ return jsonResponse(result);
149
+ }
150
+ if (path === "/api/user-profile/snapshot" && method === "GET") {
151
+ const changelogId = url.searchParams.get("chlogId");
152
+ if (!changelogId) {
153
+ return jsonResponse({ success: false, error: "changelogId parameter required" });
154
+ }
155
+ const result = await handleGetProfileSnapshot(changelogId);
156
+ return jsonResponse(result);
157
+ }
158
+ if (path === "/api/user-profile/refresh" && method === "POST") {
159
+ const body = (await req.json().catch(() => ({})));
160
+ const userId = body.userId || undefined;
161
+ const result = await handleRefreshProfile(userId);
162
+ return jsonResponse(result);
163
+ }
136
164
  return new Response("Not Found", { status: 404 });
137
165
  }
138
166
  catch (error) {