opencode-mem 2.3.2 → 2.3.4
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -52
- package/dist/services/ai/providers/anthropic-messages.d.ts +0 -1
- package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -1
- package/dist/services/ai/providers/anthropic-messages.js +30 -23
- package/dist/services/ai/providers/openai-chat-completion.d.ts +0 -1
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -1
- package/dist/services/ai/providers/openai-chat-completion.js +31 -24
- package/dist/services/ai/validators/user-profile-validator.d.ts +14 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts.map +1 -0
- package/dist/services/ai/validators/user-profile-validator.js +128 -0
- package/dist/services/auto-capture.d.ts.map +1 -1
- package/dist/services/auto-capture.js +1 -0
- package/dist/services/embedding.d.ts +2 -0
- package/dist/services/embedding.d.ts.map +1 -1
- package/dist/services/embedding.js +28 -20
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +10 -3
- package/dist/services/user-memory-learning.d.ts.map +1 -1
- package/dist/services/user-memory-learning.js +6 -1
- package/dist/services/user-profile/profile-utils.d.ts +3 -0
- package/dist/services/user-profile/profile-utils.d.ts.map +1 -0
- package/dist/services/user-profile/profile-utils.js +2 -0
- package/dist/services/user-profile/user-profile-manager.d.ts.map +1 -1
- package/dist/services/user-profile/user-profile-manager.js +9 -6
- package/dist/web/app.js +1 -3
- package/dist/web/index.html +0 -4
- package/package.json +1 -1
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;AA0C/D,eAAO,MAAM,iBAAiB,EAAE,
|
|
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,MA0e/B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,17 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
37
37
|
if (!isConfigured()) {
|
|
38
38
|
log("Plugin disabled - memory system not configured");
|
|
39
39
|
}
|
|
40
|
+
const GLOBAL_PLUGIN_WARMUP_KEY = Symbol.for("opencode-mem.plugin.warmedup");
|
|
41
|
+
if (!globalThis[GLOBAL_PLUGIN_WARMUP_KEY] && isConfigured()) {
|
|
42
|
+
try {
|
|
43
|
+
await memoryClient.warmup();
|
|
44
|
+
globalThis[GLOBAL_PLUGIN_WARMUP_KEY] = true;
|
|
45
|
+
log("Plugin warmup completed");
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
log("Plugin warmup failed", { error: String(error) });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
40
51
|
if (CONFIG.webServerEnabled) {
|
|
41
52
|
startWebServer({
|
|
42
53
|
port: CONFIG.webServerPort,
|
|
@@ -132,58 +143,6 @@ export const OpenCodeMemPlugin = async (ctx) => {
|
|
|
132
143
|
const isFirstMessage = !injectedSessions.has(input.sessionID);
|
|
133
144
|
if (isFirstMessage) {
|
|
134
145
|
injectedSessions.add(input.sessionID);
|
|
135
|
-
const needsWarmup = !(await memoryClient.isReady());
|
|
136
|
-
if (needsWarmup) {
|
|
137
|
-
if (ctx.client?.tui) {
|
|
138
|
-
await ctx.client.tui
|
|
139
|
-
.showToast({
|
|
140
|
-
body: {
|
|
141
|
-
title: "Memory System",
|
|
142
|
-
message: "Initializing (first time: 30-60s)...",
|
|
143
|
-
variant: "info",
|
|
144
|
-
duration: 5000,
|
|
145
|
-
},
|
|
146
|
-
})
|
|
147
|
-
.catch(() => { });
|
|
148
|
-
}
|
|
149
|
-
try {
|
|
150
|
-
await memoryClient.warmup();
|
|
151
|
-
if (ctx.client?.tui) {
|
|
152
|
-
const autoCaptureStatus = CONFIG.autoCaptureEnabled &&
|
|
153
|
-
CONFIG.memoryModel &&
|
|
154
|
-
CONFIG.memoryApiUrl &&
|
|
155
|
-
CONFIG.memoryApiKey
|
|
156
|
-
? "Auto-capture: enabled"
|
|
157
|
-
: "Auto-capture: disabled";
|
|
158
|
-
await ctx.client.tui
|
|
159
|
-
.showToast({
|
|
160
|
-
body: {
|
|
161
|
-
title: "Memory System Ready!",
|
|
162
|
-
message: autoCaptureStatus,
|
|
163
|
-
variant: CONFIG.autoCaptureEnabled ? "success" : "warning",
|
|
164
|
-
duration: 3000,
|
|
165
|
-
},
|
|
166
|
-
})
|
|
167
|
-
.catch(() => { });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch (warmupError) {
|
|
171
|
-
log("Warmup failed", { error: String(warmupError) });
|
|
172
|
-
if (ctx.client?.tui) {
|
|
173
|
-
await ctx.client.tui
|
|
174
|
-
.showToast({
|
|
175
|
-
body: {
|
|
176
|
-
title: "Memory System Error",
|
|
177
|
-
message: `Failed to initialize: ${String(warmupError)}`,
|
|
178
|
-
variant: "error",
|
|
179
|
-
duration: 10000,
|
|
180
|
-
},
|
|
181
|
-
})
|
|
182
|
-
.catch(() => { });
|
|
183
|
-
}
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
146
|
const projectMemoriesListResult = await memoryClient.listMemories(tags.project.tag, CONFIG.maxMemories);
|
|
188
147
|
const projectMemoriesList = projectMemoriesListResult.success
|
|
189
148
|
? projectMemoriesListResult
|
|
@@ -8,6 +8,5 @@ export declare class AnthropicMessagesProvider extends BaseAIProvider {
|
|
|
8
8
|
supportsSession(): boolean;
|
|
9
9
|
executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: ChatCompletionTool, sessionId: string): Promise<ToolCallResult>;
|
|
10
10
|
private extractToolUse;
|
|
11
|
-
private validateResponse;
|
|
12
11
|
}
|
|
13
12
|
//# sourceMappingURL=anthropic-messages.d.ts.map
|
|
@@ -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;
|
|
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;AA4BvF,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;IA2K1B,OAAO,CAAC,cAAc;CAavB"}
|
|
@@ -2,6 +2,7 @@ import { BaseAIProvider } from "./base-provider.js";
|
|
|
2
2
|
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
3
|
import { ToolSchemaConverter } from "../tools/tool-schema.js";
|
|
4
4
|
import { log } from "../../logger.js";
|
|
5
|
+
import { UserProfileValidator } from "../validators/user-profile-validator.js";
|
|
5
6
|
export class AnthropicMessagesProvider extends BaseAIProvider {
|
|
6
7
|
aiSessionManager;
|
|
7
8
|
constructor(config, aiSessionManager) {
|
|
@@ -95,11 +96,35 @@ export class AnthropicMessagesProvider extends BaseAIProvider {
|
|
|
95
96
|
});
|
|
96
97
|
const toolUse = this.extractToolUse(data, toolSchema.function.name);
|
|
97
98
|
if (toolUse) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
try {
|
|
100
|
+
const result = UserProfileValidator.validate(toolUse);
|
|
101
|
+
if (!result.valid) {
|
|
102
|
+
throw new Error(result.errors.join(", "));
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
data: result.data,
|
|
107
|
+
iterations,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (validationError) {
|
|
111
|
+
const errorStack = validationError instanceof Error ? validationError.stack : undefined;
|
|
112
|
+
log("Anthropic tool response validation failed", {
|
|
113
|
+
error: String(validationError),
|
|
114
|
+
stack: errorStack,
|
|
115
|
+
errorType: validationError instanceof Error
|
|
116
|
+
? validationError.constructor.name
|
|
117
|
+
: typeof validationError,
|
|
118
|
+
toolName: toolSchema.function.name,
|
|
119
|
+
iteration: iterations,
|
|
120
|
+
rawData: JSON.stringify(toolUse).slice(0, 500),
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: `Validation failed: ${String(validationError)}`,
|
|
125
|
+
iterations,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
103
128
|
}
|
|
104
129
|
if (data.stop_reason === "end_turn") {
|
|
105
130
|
const retrySequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
@@ -149,22 +174,4 @@ export class AnthropicMessagesProvider extends BaseAIProvider {
|
|
|
149
174
|
}
|
|
150
175
|
return null;
|
|
151
176
|
}
|
|
152
|
-
validateResponse(data) {
|
|
153
|
-
if (!data || typeof data !== "object") {
|
|
154
|
-
throw new Error("Response is not an object");
|
|
155
|
-
}
|
|
156
|
-
if (Array.isArray(data)) {
|
|
157
|
-
throw new Error("Response cannot be an array");
|
|
158
|
-
}
|
|
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
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return data;
|
|
169
|
-
}
|
|
170
177
|
}
|
|
@@ -7,6 +7,5 @@ export declare class OpenAIChatCompletionProvider extends BaseAIProvider {
|
|
|
7
7
|
getProviderName(): string;
|
|
8
8
|
supportsSession(): boolean;
|
|
9
9
|
executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: ChatCompletionTool, sessionId: string): Promise<ToolCallResult>;
|
|
10
|
-
private validateResponse;
|
|
11
10
|
}
|
|
12
11
|
//# sourceMappingURL=openai-chat-completion.d.ts.map
|
|
@@ -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;
|
|
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;AAmBlE,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;CAmM3B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BaseAIProvider } from "./base-provider.js";
|
|
2
2
|
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
3
|
import { log } from "../../logger.js";
|
|
4
|
+
import { UserProfileValidator } from "../validators/user-profile-validator.js";
|
|
4
5
|
export class OpenAIChatCompletionProvider extends BaseAIProvider {
|
|
5
6
|
aiSessionManager;
|
|
6
7
|
constructor(config, aiSessionManager) {
|
|
@@ -114,12 +115,36 @@ export class OpenAIChatCompletionProvider extends BaseAIProvider {
|
|
|
114
115
|
if (choice.message.tool_calls && choice.message.tool_calls.length > 0) {
|
|
115
116
|
const toolCall = choice.message.tool_calls[0];
|
|
116
117
|
if (toolCall && toolCall.function.name === toolSchema.function.name) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(toolCall.function.arguments);
|
|
120
|
+
const result = UserProfileValidator.validate(parsed);
|
|
121
|
+
if (!result.valid) {
|
|
122
|
+
throw new Error(result.errors.join(", "));
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
data: result.data,
|
|
127
|
+
iterations,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch (validationError) {
|
|
131
|
+
const errorStack = validationError instanceof Error ? validationError.stack : undefined;
|
|
132
|
+
log("OpenAI tool response validation failed", {
|
|
133
|
+
error: String(validationError),
|
|
134
|
+
stack: errorStack,
|
|
135
|
+
errorType: validationError instanceof Error
|
|
136
|
+
? validationError.constructor.name
|
|
137
|
+
: typeof validationError,
|
|
138
|
+
toolName: toolSchema.function.name,
|
|
139
|
+
iteration: iterations,
|
|
140
|
+
rawArguments: toolCall.function.arguments.slice(0, 500),
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: `Validation failed: ${String(validationError)}`,
|
|
145
|
+
iterations,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
123
148
|
}
|
|
124
149
|
}
|
|
125
150
|
const retrySequence = this.aiSessionManager.getLastSequence(session.id) + 1;
|
|
@@ -154,22 +179,4 @@ export class OpenAIChatCompletionProvider extends BaseAIProvider {
|
|
|
154
179
|
iterations,
|
|
155
180
|
};
|
|
156
181
|
}
|
|
157
|
-
validateResponse(data) {
|
|
158
|
-
if (!data || typeof data !== "object") {
|
|
159
|
-
throw new Error("Response is not an object");
|
|
160
|
-
}
|
|
161
|
-
if (Array.isArray(data)) {
|
|
162
|
-
throw new Error("Response cannot be an array");
|
|
163
|
-
}
|
|
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
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return data;
|
|
174
|
-
}
|
|
175
182
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UserProfileData } from "../../user-profile/types.js";
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
data?: UserProfileData;
|
|
6
|
+
}
|
|
7
|
+
export declare class UserProfileValidator {
|
|
8
|
+
static validate(data: any): ValidationResult;
|
|
9
|
+
private static validatePreferences;
|
|
10
|
+
private static validatePatterns;
|
|
11
|
+
private static validateWorkflows;
|
|
12
|
+
private static validateSkillLevel;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=user-profile-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-profile-validator.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/validators/user-profile-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,gBAAgB;IA0C5C,OAAO,CAAC,MAAM,CAAC,mBAAmB;IA6BlC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAqB/B,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAuBhC,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAalC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export class UserProfileValidator {
|
|
2
|
+
static validate(data) {
|
|
3
|
+
const errors = [];
|
|
4
|
+
if (!data || typeof data !== "object") {
|
|
5
|
+
return { valid: false, errors: ["Response is not an object"] };
|
|
6
|
+
}
|
|
7
|
+
if (Array.isArray(data)) {
|
|
8
|
+
return { valid: false, errors: ["Response cannot be an array"] };
|
|
9
|
+
}
|
|
10
|
+
const keys = Object.keys(data);
|
|
11
|
+
if (keys.length === 0) {
|
|
12
|
+
return { valid: false, errors: ["Response object is empty"] };
|
|
13
|
+
}
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
if (data[key] === undefined || data[key] === null) {
|
|
16
|
+
errors.push(`Field '${key}' is null or undefined`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (errors.length > 0) {
|
|
20
|
+
return { valid: false, errors };
|
|
21
|
+
}
|
|
22
|
+
if (data.preferences) {
|
|
23
|
+
const prefErrors = this.validatePreferences(data.preferences);
|
|
24
|
+
errors.push(...prefErrors);
|
|
25
|
+
}
|
|
26
|
+
if (data.patterns) {
|
|
27
|
+
const patternErrors = this.validatePatterns(data.patterns);
|
|
28
|
+
errors.push(...patternErrors);
|
|
29
|
+
}
|
|
30
|
+
if (data.workflows) {
|
|
31
|
+
const workflowErrors = this.validateWorkflows(data.workflows);
|
|
32
|
+
errors.push(...workflowErrors);
|
|
33
|
+
}
|
|
34
|
+
if (data.skillLevel) {
|
|
35
|
+
const skillErrors = this.validateSkillLevel(data.skillLevel);
|
|
36
|
+
errors.push(...skillErrors);
|
|
37
|
+
}
|
|
38
|
+
if (errors.length > 0) {
|
|
39
|
+
return { valid: false, errors };
|
|
40
|
+
}
|
|
41
|
+
return { valid: true, errors: [], data: data };
|
|
42
|
+
}
|
|
43
|
+
static validatePreferences(preferences) {
|
|
44
|
+
const errors = [];
|
|
45
|
+
if (!Array.isArray(preferences)) {
|
|
46
|
+
return ["preferences must be an array"];
|
|
47
|
+
}
|
|
48
|
+
for (let i = 0; i < preferences.length; i++) {
|
|
49
|
+
const pref = preferences[i];
|
|
50
|
+
if (!pref || typeof pref !== "object") {
|
|
51
|
+
errors.push(`preferences[${i}] is not an object`);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!pref.category || typeof pref.category !== "string") {
|
|
55
|
+
errors.push(`preferences[${i}].category is missing or invalid`);
|
|
56
|
+
}
|
|
57
|
+
if (!pref.description || typeof pref.description !== "string") {
|
|
58
|
+
errors.push(`preferences[${i}].description is missing or invalid`);
|
|
59
|
+
}
|
|
60
|
+
if (typeof pref.confidence !== "number") {
|
|
61
|
+
errors.push(`preferences[${i}].confidence is missing or invalid`);
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(pref.evidence)) {
|
|
64
|
+
errors.push(`preferences[${i}].evidence must be an array`);
|
|
65
|
+
}
|
|
66
|
+
else if (pref.evidence.length === 0) {
|
|
67
|
+
errors.push(`preferences[${i}].evidence cannot be empty`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return errors;
|
|
71
|
+
}
|
|
72
|
+
static validatePatterns(patterns) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
if (!Array.isArray(patterns)) {
|
|
75
|
+
return ["patterns must be an array"];
|
|
76
|
+
}
|
|
77
|
+
for (let i = 0; i < patterns.length; i++) {
|
|
78
|
+
const pattern = patterns[i];
|
|
79
|
+
if (!pattern || typeof pattern !== "object") {
|
|
80
|
+
errors.push(`patterns[${i}] is not an object`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!pattern.category || typeof pattern.category !== "string") {
|
|
84
|
+
errors.push(`patterns[${i}].category is missing or invalid`);
|
|
85
|
+
}
|
|
86
|
+
if (!pattern.description || typeof pattern.description !== "string") {
|
|
87
|
+
errors.push(`patterns[${i}].description is missing or invalid`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return errors;
|
|
91
|
+
}
|
|
92
|
+
static validateWorkflows(workflows) {
|
|
93
|
+
const errors = [];
|
|
94
|
+
if (!Array.isArray(workflows)) {
|
|
95
|
+
return ["workflows must be an array"];
|
|
96
|
+
}
|
|
97
|
+
for (let i = 0; i < workflows.length; i++) {
|
|
98
|
+
const workflow = workflows[i];
|
|
99
|
+
if (!workflow || typeof workflow !== "object") {
|
|
100
|
+
errors.push(`workflows[${i}] is not an object`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!workflow.description || typeof workflow.description !== "string") {
|
|
104
|
+
errors.push(`workflows[${i}].description is missing or invalid`);
|
|
105
|
+
}
|
|
106
|
+
if (!Array.isArray(workflow.steps)) {
|
|
107
|
+
errors.push(`workflows[${i}].steps must be an array`);
|
|
108
|
+
}
|
|
109
|
+
else if (workflow.steps.length === 0) {
|
|
110
|
+
errors.push(`workflows[${i}].steps cannot be empty`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return errors;
|
|
114
|
+
}
|
|
115
|
+
static validateSkillLevel(skillLevel) {
|
|
116
|
+
const errors = [];
|
|
117
|
+
if (typeof skillLevel !== "object") {
|
|
118
|
+
return ["skillLevel must be an object"];
|
|
119
|
+
}
|
|
120
|
+
if (!skillLevel.overall || typeof skillLevel.overall !== "string") {
|
|
121
|
+
errors.push("skillLevel.overall is missing or invalid");
|
|
122
|
+
}
|
|
123
|
+
if (!skillLevel.domains || typeof skillLevel.domains !== "object") {
|
|
124
|
+
errors.push("skillLevel.domains must be an object");
|
|
125
|
+
}
|
|
126
|
+
return errors;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-capture.d.ts","sourceRoot":"","sources":["../../src/services/auto-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAcvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"auto-capture.d.ts","sourceRoot":"","sources":["../../src/services/auto-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAcvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgGf"}
|
|
@@ -45,6 +45,7 @@ export async function performAutoCapture(ctx, sessionID, directory) {
|
|
|
45
45
|
}
|
|
46
46
|
const result = await memoryClient.addMemory(summaryResult.summary, tags.project.tag, {
|
|
47
47
|
source: "auto-capture",
|
|
48
|
+
type: summaryResult.type,
|
|
48
49
|
sessionID,
|
|
49
50
|
promptId: prompt.id,
|
|
50
51
|
captureTimestamp: Date.now(),
|
|
@@ -2,7 +2,9 @@ export declare class EmbeddingService {
|
|
|
2
2
|
private pipe;
|
|
3
3
|
private initPromise;
|
|
4
4
|
isWarmedUp: boolean;
|
|
5
|
+
static getInstance(): EmbeddingService;
|
|
5
6
|
warmup(progressCallback?: (progress: any) => void): Promise<void>;
|
|
7
|
+
private initializeModel;
|
|
6
8
|
embed(text: string): Promise<Float32Array>;
|
|
7
9
|
embedWithTimeout(text: string): Promise<Float32Array>;
|
|
8
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../src/services/embedding.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../src/services/embedding.ts"],"names":[],"mappings":"AAkBA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,WAAW,CAA8B;IAC1C,UAAU,EAAE,OAAO,CAAS;IAEnC,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAOhC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAQzD,eAAe;IAuBvB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAkC1C,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CAG5D;AAED,eAAO,MAAM,gBAAgB,kBAAiC,CAAC"}
|
|
@@ -5,6 +5,7 @@ env.allowLocalModels = true;
|
|
|
5
5
|
env.allowRemoteModels = true;
|
|
6
6
|
env.cacheDir = CONFIG.storagePath + "/.cache";
|
|
7
7
|
const TIMEOUT_MS = 30000;
|
|
8
|
+
const GLOBAL_EMBEDDING_KEY = Symbol.for("opencode-mem.embedding.instance");
|
|
8
9
|
function withTimeout(promise, ms) {
|
|
9
10
|
return Promise.race([
|
|
10
11
|
promise,
|
|
@@ -15,32 +16,39 @@ export class EmbeddingService {
|
|
|
15
16
|
pipe = null;
|
|
16
17
|
initPromise = null;
|
|
17
18
|
isWarmedUp = false;
|
|
19
|
+
static getInstance() {
|
|
20
|
+
if (!globalThis[GLOBAL_EMBEDDING_KEY]) {
|
|
21
|
+
globalThis[GLOBAL_EMBEDDING_KEY] = new EmbeddingService();
|
|
22
|
+
}
|
|
23
|
+
return globalThis[GLOBAL_EMBEDDING_KEY];
|
|
24
|
+
}
|
|
18
25
|
async warmup(progressCallback) {
|
|
19
26
|
if (this.isWarmedUp)
|
|
20
27
|
return;
|
|
21
28
|
if (this.initPromise)
|
|
22
29
|
return this.initPromise;
|
|
23
|
-
this.initPromise = (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
log("Downloading embedding model", { model: CONFIG.embeddingModel });
|
|
31
|
-
this.pipe = await pipeline("feature-extraction", CONFIG.embeddingModel, {
|
|
32
|
-
progress_callback: progressCallback,
|
|
33
|
-
});
|
|
30
|
+
this.initPromise = this.initializeModel(progressCallback);
|
|
31
|
+
return this.initPromise;
|
|
32
|
+
}
|
|
33
|
+
async initializeModel(progressCallback) {
|
|
34
|
+
try {
|
|
35
|
+
if (CONFIG.embeddingApiUrl && CONFIG.embeddingApiKey) {
|
|
36
|
+
log("Using OpenAI-compatible API for embeddings");
|
|
34
37
|
this.isWarmedUp = true;
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
this.initPromise = null;
|
|
39
|
-
log("Failed to initialize embedding model", { error: String(error) });
|
|
40
|
-
throw error;
|
|
38
|
+
return;
|
|
41
39
|
}
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
log("Downloading embedding model", { model: CONFIG.embeddingModel });
|
|
41
|
+
this.pipe = await pipeline("feature-extraction", CONFIG.embeddingModel, {
|
|
42
|
+
progress_callback: progressCallback,
|
|
43
|
+
});
|
|
44
|
+
this.isWarmedUp = true;
|
|
45
|
+
log("Embedding model ready");
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
this.initPromise = null;
|
|
49
|
+
log("Failed to initialize embedding model", { error: String(error) });
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
44
52
|
}
|
|
45
53
|
async embed(text) {
|
|
46
54
|
if (!this.isWarmedUp && !this.initPromise) {
|
|
@@ -74,4 +82,4 @@ export class EmbeddingService {
|
|
|
74
82
|
return withTimeout(this.embed(text), TIMEOUT_MS);
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
|
-
export const embeddingService =
|
|
85
|
+
export const embeddingService = EmbeddingService.getInstance();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/services/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/services/logger.ts"],"names":[],"mappings":"AAkBA,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,QAOlD"}
|
package/dist/services/logger.js
CHANGED
|
@@ -3,11 +3,18 @@ import { homedir } from "os";
|
|
|
3
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
|
-
|
|
7
|
-
|
|
6
|
+
const GLOBAL_LOGGER_KEY = Symbol.for("opencode-mem.logger.initialized");
|
|
7
|
+
function ensureLoggerInitialized() {
|
|
8
|
+
if (globalThis[GLOBAL_LOGGER_KEY])
|
|
9
|
+
return;
|
|
10
|
+
if (!existsSync(LOG_DIR)) {
|
|
11
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
writeFileSync(LOG_FILE, `\n--- Session started: ${new Date().toISOString()} ---\n`, { flag: "a" });
|
|
14
|
+
globalThis[GLOBAL_LOGGER_KEY] = true;
|
|
8
15
|
}
|
|
9
|
-
writeFileSync(LOG_FILE, `\n--- Session started: ${new Date().toISOString()} ---\n`, { flag: "a" });
|
|
10
16
|
export function log(message, data) {
|
|
17
|
+
ensureLoggerInitialized();
|
|
11
18
|
const timestamp = new Date().toISOString();
|
|
12
19
|
const line = data
|
|
13
20
|
? `[${timestamp}] ${message}: ${JSON.stringify(data)}\n`
|
|
@@ -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,0BAA0B,CAC9C,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,
|
|
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,CAmFf"}
|
|
@@ -44,7 +44,12 @@ export async function performUserProfileLearning(ctx, directory) {
|
|
|
44
44
|
.catch(() => { });
|
|
45
45
|
}
|
|
46
46
|
catch (error) {
|
|
47
|
-
|
|
47
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
48
|
+
log("User memory learning error", {
|
|
49
|
+
error: String(error),
|
|
50
|
+
stack: errorStack,
|
|
51
|
+
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
52
|
+
});
|
|
48
53
|
await ctx.client?.tui
|
|
49
54
|
.showToast({
|
|
50
55
|
body: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-utils.d.ts","sourceRoot":"","sources":["../../../src/services/user-profile/profile-utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,KAAG,CAAC,EAAe,CAAC;AAC5E,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,UAAU,CAAC,KAAG,CACrE,CAAC"}
|
|
@@ -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,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,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;AAKrF,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;CA2FhG;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { Database } from "bun:sqlite";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { connectionManager } from "../sqlite/connection-manager.js";
|
|
4
4
|
import { CONFIG } from "../../config.js";
|
|
5
|
+
import { safeArray, safeObject } from "./profile-utils.js";
|
|
5
6
|
const USER_PROFILES_DB_NAME = "user-profiles.db";
|
|
6
7
|
export class UserProfileManager {
|
|
7
8
|
db;
|
|
@@ -190,10 +191,10 @@ export class UserProfileManager {
|
|
|
190
191
|
}
|
|
191
192
|
mergeProfileData(existing, updates) {
|
|
192
193
|
const merged = {
|
|
193
|
-
preferences:
|
|
194
|
-
patterns:
|
|
195
|
-
workflows:
|
|
196
|
-
skillLevel:
|
|
194
|
+
preferences: safeArray(existing?.preferences),
|
|
195
|
+
patterns: safeArray(existing?.patterns),
|
|
196
|
+
workflows: safeArray(existing?.workflows),
|
|
197
|
+
skillLevel: safeObject(existing?.skillLevel, { overall: "intermediate", domains: {} }),
|
|
197
198
|
};
|
|
198
199
|
if (updates.preferences) {
|
|
199
200
|
for (const newPref of updates.preferences) {
|
|
@@ -204,7 +205,9 @@ export class UserProfileManager {
|
|
|
204
205
|
merged.preferences[existingIndex] = {
|
|
205
206
|
...newPref,
|
|
206
207
|
confidence: Math.min(1, existing.confidence + 0.1),
|
|
207
|
-
evidence: [
|
|
208
|
+
evidence: [
|
|
209
|
+
...new Set([...safeArray(existing.evidence), ...safeArray(newPref.evidence)]),
|
|
210
|
+
].slice(0, 5),
|
|
208
211
|
lastUpdated: Date.now(),
|
|
209
212
|
};
|
|
210
213
|
}
|
|
@@ -258,7 +261,7 @@ export class UserProfileManager {
|
|
|
258
261
|
if (updates.skillLevel) {
|
|
259
262
|
merged.skillLevel = {
|
|
260
263
|
overall: updates.skillLevel.overall || merged.skillLevel.overall,
|
|
261
|
-
domains: { ...merged.skillLevel.domains, ...updates.skillLevel.domains },
|
|
264
|
+
domains: { ...merged.skillLevel.domains, ...safeObject(updates.skillLevel.domains, {}) },
|
|
262
265
|
};
|
|
263
266
|
}
|
|
264
267
|
return merged;
|
package/dist/web/app.js
CHANGED
|
@@ -413,7 +413,6 @@ function editMemory(id) {
|
|
|
413
413
|
if (!memory) return;
|
|
414
414
|
|
|
415
415
|
document.getElementById("edit-id").value = memory.id;
|
|
416
|
-
document.getElementById("edit-type").value = memory.memoryType || "";
|
|
417
416
|
document.getElementById("edit-content").value = memory.content;
|
|
418
417
|
|
|
419
418
|
document.getElementById("edit-modal").classList.remove("hidden");
|
|
@@ -423,7 +422,6 @@ async function saveEdit(e) {
|
|
|
423
422
|
e.preventDefault();
|
|
424
423
|
|
|
425
424
|
const id = document.getElementById("edit-id").value;
|
|
426
|
-
const type = document.getElementById("edit-type").value.trim();
|
|
427
425
|
const content = document.getElementById("edit-content").value.trim();
|
|
428
426
|
|
|
429
427
|
if (!content) {
|
|
@@ -434,7 +432,7 @@ async function saveEdit(e) {
|
|
|
434
432
|
const result = await fetchAPI(`/api/memories/${id}`, {
|
|
435
433
|
method: "PUT",
|
|
436
434
|
headers: { "Content-Type": "application/json" },
|
|
437
|
-
body: JSON.stringify({ content
|
|
435
|
+
body: JSON.stringify({ content }),
|
|
438
436
|
});
|
|
439
437
|
|
|
440
438
|
if (result.success) {
|
package/dist/web/index.html
CHANGED
|
@@ -181,10 +181,6 @@
|
|
|
181
181
|
</div>
|
|
182
182
|
<form id="edit-form">
|
|
183
183
|
<input type="hidden" id="edit-id" />
|
|
184
|
-
<div class="form-group">
|
|
185
|
-
<label>Type:</label>
|
|
186
|
-
<input type="text" id="edit-type" />
|
|
187
|
-
</div>
|
|
188
184
|
<div class="form-group">
|
|
189
185
|
<label>Content:</label>
|
|
190
186
|
<textarea id="edit-content" rows="6" required></textarea>
|