opencode-mem 2.16.0 → 2.17.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.
@@ -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;AAmB/D,eAAO,MAAM,iBAAiB,EAAE,MA4hB/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;AAmB/D,eAAO,MAAM,iBAAiB,EAAE,MAkiB/B,CAAC"}
package/dist/index.js CHANGED
@@ -114,12 +114,15 @@ export const OpenCodeMemPlugin = async (ctx) => {
114
114
  }
115
115
  });
116
116
  }
117
+ const cleanupPlugin = async () => {
118
+ if (webServer)
119
+ await webServer.stop();
120
+ if (memoryClient)
121
+ memoryClient.close();
122
+ };
117
123
  const shutdownHandler = async () => {
118
124
  try {
119
- if (webServer) {
120
- await webServer.stop();
121
- }
122
- memoryClient.close();
125
+ await cleanupPlugin();
123
126
  process.exit(0);
124
127
  }
125
128
  catch (error) {
@@ -129,6 +132,12 @@ export const OpenCodeMemPlugin = async (ctx) => {
129
132
  };
130
133
  process.on("SIGINT", shutdownHandler);
131
134
  process.on("SIGTERM", shutdownHandler);
135
+ process.on("exit", () => {
136
+ if (webServer)
137
+ webServer.stop().catch(() => { });
138
+ if (memoryClient)
139
+ memoryClient.close();
140
+ });
132
141
  return {
133
142
  "chat.message": async (input, output) => {
134
143
  if (!isConfigured() || !CONFIG.chatMessage.enabled)
@@ -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,EACL,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAgElE,qBAAa,4BAA6B,SAAQ,cAAc;IAC9D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;gBAExC,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB;IAKtE,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAI1B,OAAO,CAAC,eAAe;IAqBvB,SAAS,CAAC,iCAAiC,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE;IAwCzE,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;CAmS3B"}
1
+ {"version":3,"file":"openai-chat-completion.d.ts","sourceRoot":"","sources":["../../../../src/services/ai/providers/openai-chat-completion.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AA+ElE,qBAAa,4BAA6B,SAAQ,cAAc;IAC9D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;gBAExC,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB;IAKtE,eAAe,IAAI,MAAM;IAIzB,eAAe,IAAI,OAAO;IAI1B,OAAO,CAAC,eAAe;IAqBvB,SAAS,CAAC,iCAAiC,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE;IAwCzE,eAAe,CACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;CAkT3B"}
@@ -25,6 +25,23 @@ function hasNonEmptyChoices(data) {
25
25
  return false;
26
26
  return true;
27
27
  }
28
+ function extractFirstJSON(raw) {
29
+ let depth = 0;
30
+ let start = -1;
31
+ for (let i = 0; i < raw.length; i++) {
32
+ if (raw[i] === "{" || raw[i] === "[") {
33
+ if (depth === 0)
34
+ start = i;
35
+ depth++;
36
+ }
37
+ else if (raw[i] === "}" || raw[i] === "]") {
38
+ depth--;
39
+ if (depth === 0)
40
+ return raw.slice(start, i + 1);
41
+ }
42
+ }
43
+ return null;
44
+ }
28
45
  export class OpenAIChatCompletionProvider extends BaseAIProvider {
29
46
  aiSessionManager;
30
47
  constructor(config, aiSessionManager) {
@@ -133,6 +150,7 @@ export class OpenAIChatCompletionProvider extends BaseAIProvider {
133
150
  let iterations = 0;
134
151
  const maxIterations = this.config.maxIterations ?? 5;
135
152
  const iterationTimeout = this.config.iterationTimeout ?? 30000;
153
+ let lastErrorMessage = "";
136
154
  while (iterations < maxIterations) {
137
155
  iterations++;
138
156
  const controller = new AbortController();
@@ -245,7 +263,21 @@ export class OpenAIChatCompletionProvider extends BaseAIProvider {
245
263
  const toolCallId = toolCall.id;
246
264
  if (toolCall.function.name === toolSchema.function.name) {
247
265
  try {
248
- const parsed = JSON.parse(toolCall.function.arguments);
266
+ const parsed = (() => {
267
+ const raw = toolCall.function.arguments;
268
+ if (typeof raw !== "string") {
269
+ return JSON.parse(JSON.stringify(raw));
270
+ }
271
+ try {
272
+ return JSON.parse(raw);
273
+ }
274
+ catch (e1) {
275
+ const fixed = extractFirstJSON(raw);
276
+ if (fixed)
277
+ return JSON.parse(fixed);
278
+ throw e1;
279
+ }
280
+ })();
249
281
  const result = UserProfileValidator.validate(parsed);
250
282
  if (!result.valid) {
251
283
  throw new Error(result.errors.join(", "));
@@ -270,6 +302,7 @@ export class OpenAIChatCompletionProvider extends BaseAIProvider {
270
302
  rawArguments: toolCall.function.arguments.slice(0, 500),
271
303
  });
272
304
  const errorMessage = `Validation failed: ${String(validationError)}`;
305
+ lastErrorMessage = errorMessage;
273
306
  this.addToolResponse(session.id, messages, toolCallId, JSON.stringify({ success: false, error: errorMessage }));
274
307
  return {
275
308
  success: false,
@@ -284,7 +317,9 @@ export class OpenAIChatCompletionProvider extends BaseAIProvider {
284
317
  }
285
318
  }
286
319
  const retrySequence = this.aiSessionManager.getLastSequence(session.id) + 1;
287
- const retryPrompt = "Please use the save_memories tool to extract and save the memories from the conversation as instructed.";
320
+ const retryPrompt = lastErrorMessage
321
+ ? `Your previous attempt failed. Error: ${lastErrorMessage}. Please fix the JSON in your tool call arguments and try again. Output ONLY valid JSON, no extra text outside the JSON structure.`
322
+ : "Please use the tool to extract and save the data as instructed.";
288
323
  this.aiSessionManager.addMessage({
289
324
  aiSessionId: session.id,
290
325
  sequence: retrySequence,
@@ -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;AAgBvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA2Ff"}
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;AAgBvD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA+Hf"}
@@ -9,6 +9,13 @@ export async function performAutoCapture(ctx, sessionID, directory) {
9
9
  if (isCaptureRunning)
10
10
  return;
11
11
  isCaptureRunning = true;
12
+ // Tracks the prompt currently held in the captured=2 in-progress state.
13
+ // Any code path between claimPrompt() and a terminal action (markAsCaptured
14
+ // or deletePrompt) MUST leave this set so the finally block can release the
15
+ // lock. Without this, transient failures (no AI response yet, missing
16
+ // client, LLM error, plugin restart) leave the row stuck at captured=2 and
17
+ // block all future captures of that prompt.
18
+ let claimedPromptId = null;
12
19
  try {
13
20
  const prompt = userPromptManager.getLastUncapturedPrompt(sessionID);
14
21
  if (!prompt) {
@@ -17,6 +24,7 @@ export async function performAutoCapture(ctx, sessionID, directory) {
17
24
  if (!userPromptManager.claimPrompt(prompt.id)) {
18
25
  return;
19
26
  }
27
+ claimedPromptId = prompt.id;
20
28
  if (!ctx.client) {
21
29
  throw new Error("Client not available");
22
30
  }
@@ -45,9 +53,18 @@ export async function performAutoCapture(ctx, sessionID, directory) {
45
53
  const summaryResult = await generateSummary(context, sessionID, prompt.content);
46
54
  if (!summaryResult || summaryResult.type === "skip") {
47
55
  userPromptManager.deletePrompt(prompt.id);
56
+ claimedPromptId = null;
48
57
  return;
49
58
  }
50
- const result = await memoryClient.addMemory(summaryResult.summary, tags.project.tag, {
59
+ // Append a "Tags: ..." footer to the summary before persisting. Tags are
60
+ // already embedded separately into tagsVector, but the contentVector never
61
+ // sees them. Inlining them here lets the 0.6-weight content channel also
62
+ // contribute when a query mentions a tag keyword, making recall noticeably
63
+ // less brittle for precise lookups (e.g. searching for a single API name).
64
+ const summaryWithTags = summaryResult.tags && summaryResult.tags.length > 0
65
+ ? `${summaryResult.summary}\n\nTags: ${summaryResult.tags.join(", ")}`
66
+ : summaryResult.summary;
67
+ const result = await memoryClient.addMemory(summaryWithTags, tags.project.tag, {
51
68
  source: "auto-capture",
52
69
  type: summaryResult.type,
53
70
  tags: summaryResult.tags,
@@ -64,6 +81,7 @@ export async function performAutoCapture(ctx, sessionID, directory) {
64
81
  if (result.success) {
65
82
  userPromptManager.linkMemoryToPrompt(prompt.id, result.id);
66
83
  userPromptManager.markAsCaptured(prompt.id);
84
+ claimedPromptId = null;
67
85
  if (CONFIG.showAutoCaptureToasts) {
68
86
  await ctx.client?.tui
69
87
  .showToast({
@@ -79,6 +97,19 @@ export async function performAutoCapture(ctx, sessionID, directory) {
79
97
  }
80
98
  }
81
99
  finally {
100
+ // Release any in-progress claim that did not reach a terminal state.
101
+ // This covers both early returns (no AI response yet, missing client,
102
+ // empty content) and exceptions (LLM failure, network error). Without
103
+ // releasing here, the prompt would remain locked at captured=2 with no
104
+ // automatic recovery path until the next plugin restart.
105
+ if (claimedPromptId !== null) {
106
+ try {
107
+ userPromptManager.releaseClaim(claimedPromptId);
108
+ }
109
+ catch (releaseErr) {
110
+ log(`Failed to release captured=2 claim for prompt ${claimedPromptId}: ${releaseErr instanceof Error ? releaseErr.message : String(releaseErr)}`);
111
+ }
112
+ }
82
113
  isCaptureRunning = false;
83
114
  }
84
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,cAAc,CAAC;AAyDrD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;IAiBlB,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjC,SAAS,IAAI;QACX,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;QACrB,KAAK,EAAE,OAAO,CAAC;KAChB;IAQD,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,GAAE,WAAuB;;;;;;;;;;;;;IA6BlF,SAAS,CACb,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB;;;;;;;;;IAyGG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA2B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK,EAAE,KAAK,GAAE,WAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2D7E,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4C5F;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,cAAc,CAAC;AAyDrD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;IAiBlB,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjC,SAAS,IAAI;QACX,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;QACrB,KAAK,EAAE,OAAO,CAAC;KAChB;IAQD,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,GAAE,WAAuB;;;;;;;;;;;;;IA6BlF,SAAS,CACb,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB;;;;;;;;;IA+GG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA2B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK,EAAE,KAAK,GAAE,WAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2D7E,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4C5F;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
@@ -111,7 +111,13 @@ export class LocalMemoryClient {
111
111
  const vector = await embeddingService.embedWithTimeout(content);
112
112
  let tagsVector = undefined;
113
113
  if (tags.length > 0) {
114
- tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
114
+ // Wrap tags in a natural-language template before embedding. Bare comma
115
+ // lists like "react, auth, bug-fix" sit outside the multilingual-e5
116
+ // training distribution, so the resulting tagsVector drifts toward
117
+ // unrelated chatter and weakens the 0.4-weight tag boost in
118
+ // VectorSearch#searchInShard. The "Topics: ..." prefix is a sentence
119
+ // form e5 was trained on and yields a more discriminative vector.
120
+ tagsVector = await embeddingService.embedWithTimeout(`Topics: ${tags.join(", ")}`);
115
121
  }
116
122
  const { scope, hash } = extractScopeFromContainerTag(containerTag);
117
123
  const shard = shardManager.getWriteShard(scope, hash);
@@ -7,9 +7,9 @@ const MAX_CACHE_SIZE = 100;
7
7
  let _transformers = null;
8
8
  function getTransformersPackageSpecifier() {
9
9
  // Keep this non-literal so OpenCode/Bun plugin-loader bundling does not eagerly
10
- // traverse @xenova/transformers internals during plugin startup. The package
10
+ // traverse @huggingface/transformers internals during plugin startup. The package
11
11
  // is only needed for the local embedding backend, and should stay lazy.
12
- return ["@xenova", "transformers"].join("/");
12
+ return ["@huggingface", "transformers"].join("/");
13
13
  }
14
14
  async function ensureTransformersLoaded() {
15
15
  if (_transformers !== null)
@@ -19,6 +19,16 @@ export declare class UserPromptManager {
19
19
  deletePrompt(promptId: string): void;
20
20
  markAsCaptured(promptId: string): void;
21
21
  claimPrompt(promptId: string): boolean;
22
+ /**
23
+ * Release a previously claimed prompt back to the pending state so it can
24
+ * be retried by a future capture cycle. Used when capture aborts before
25
+ * either successfully writing a memory or explicitly skipping the prompt
26
+ * (e.g. transient network error, missing AI response, plugin restart).
27
+ *
28
+ * Only rows still marked as in-progress (captured = 2) are touched, so
29
+ * concurrent callers that already finished the capture cannot be reverted.
30
+ */
31
+ releaseClaim(promptId: string): boolean;
22
32
  countUncapturedPrompts(): number;
23
33
  getUncapturedPrompts(limit: number): UserPrompt[];
24
34
  markMultipleAsCaptured(promptIds: string[]): void;
@@ -1 +1 @@
1
- {"version":3,"file":"user-prompt-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-prompt/user-prompt-manager.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IAiCpB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAa9F,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAc7D,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKpC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKtC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQtC,sBAAsB,IAAI,MAAM;IAMhC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAUjD,8BAA8B,IAAI,MAAM;IAQxC,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYtD,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKlD,kCAAkC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAU7D,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE;IAiBpF,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK5D,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAOlD,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAgBtD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,UAAU,EAAE;IAiBpF,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE;IAQ5C,OAAO,CAAC,WAAW;CAapB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
1
+ {"version":3,"file":"user-prompt-manager.d.ts","sourceRoot":"","sources":["../../../src/services/user-prompt/user-prompt-manager.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;;IAQhC,OAAO,CAAC,YAAY;IAiCpB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAa9F,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAc7D,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKpC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKtC,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQtC;;;;;;;;OAQG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQvC,sBAAsB,IAAI,MAAM;IAMhC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAUjD,8BAA8B,IAAI,MAAM;IAQxC,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE;IAYtD,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKlD,kCAAkC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAU7D,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE;IAiBpF,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK5D,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAOlD,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE;IAgBtD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,UAAU,EAAE;IAiBpF,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE;IAQ5C,OAAO,CAAC,WAAW;CAapB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
@@ -69,6 +69,20 @@ export class UserPromptManager {
69
69
  const result = stmt.run(promptId);
70
70
  return result.changes > 0;
71
71
  }
72
+ /**
73
+ * Release a previously claimed prompt back to the pending state so it can
74
+ * be retried by a future capture cycle. Used when capture aborts before
75
+ * either successfully writing a memory or explicitly skipping the prompt
76
+ * (e.g. transient network error, missing AI response, plugin restart).
77
+ *
78
+ * Only rows still marked as in-progress (captured = 2) are touched, so
79
+ * concurrent callers that already finished the capture cannot be reverted.
80
+ */
81
+ releaseClaim(promptId) {
82
+ const stmt = this.db.prepare(`UPDATE user_prompts SET captured = 0 WHERE id = ? AND captured = 2`);
83
+ const result = stmt.run(promptId);
84
+ return result.changes > 0;
85
+ }
72
86
  countUncapturedPrompts() {
73
87
  const stmt = this.db.prepare(`SELECT COUNT(*) as count FROM user_prompts WHERE captured = 0`);
74
88
  const row = stmt.get();
@@ -1 +1 @@
1
- {"version":3,"file":"web-server.d.ts","sourceRoot":"","sources":["../../src/services/web-server.ts"],"names":[],"mappings":"AA2HA,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,mBAAmB,CAA+B;IAC1D,OAAO,CAAC,kBAAkB,CAAsC;gBAEpD,MAAM,EAAE,eAAe;IAInC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YASd,MAAM;IAgCpB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,mBAAmB;YAOb,eAAe;IA+BvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,SAAS,IAAI,OAAO;IAIpB,aAAa,IAAI,OAAO;IAIxB,MAAM,IAAI,MAAM;IAIV,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;YAchC,aAAa;IAuN3B,OAAO,CAAC,eAAe;IA4BvB,OAAO,CAAC,YAAY;CAWrB;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAIhF"}
1
+ {"version":3,"file":"web-server.d.ts","sourceRoot":"","sources":["../../src/services/web-server.ts"],"names":[],"mappings":"AAqIA,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,mBAAmB,CAA+B;IAC1D,OAAO,CAAC,kBAAkB,CAAsC;gBAEpD,MAAM,EAAE,eAAe;IAInC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YASd,MAAM;IAgCpB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,mBAAmB;YAOb,eAAe;IA+BvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,SAAS,IAAI,OAAO;IAIpB,aAAa,IAAI,OAAO;IAIxB,MAAM,IAAI,MAAM;IAIV,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;YAchC,aAAa;IAuN3B,OAAO,CAAC,eAAe;IA4BvB,OAAO,CAAC,YAAY;CAWrB;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAIhF"}
@@ -35,6 +35,7 @@ function serveFetch(opts) {
35
35
  const webRes = await opts.fetch(webReq);
36
36
  res.statusCode = webRes.status;
37
37
  webRes.headers.forEach((value, name) => res.setHeader(name, value));
38
+ res.setHeader("Connection", "close");
38
39
  if (webRes.body) {
39
40
  Readable.fromWeb(webRes.body).pipe(res);
40
41
  }
@@ -58,11 +59,20 @@ function serveFetch(opts) {
58
59
  listenError = err;
59
60
  }
60
61
  });
61
- server.listen(opts.port, opts.hostname);
62
+ server.listen({ port: opts.port, host: opts.hostname, reuseAddr: true });
63
+ server.unref();
64
+ server.timeout = 30000;
65
+ server.keepAliveTimeout = 10000;
66
+ server.headersTimeout = 11000;
62
67
  if (listenError) {
63
68
  throw listenError;
64
69
  }
65
- return { stop: () => server.close() };
70
+ return {
71
+ stop: () => {
72
+ server.closeAllConnections();
73
+ server.close();
74
+ },
75
+ };
66
76
  }
67
77
  const __filename = fileURLToPath(import.meta.url);
68
78
  const __dirname = dirname(__filename);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-mem",
3
- "version": "2.16.0",
3
+ "version": "2.17.1",
4
4
  "description": "OpenCode plugin that gives coding agents persistent memory using local vector database",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",
@@ -53,7 +53,7 @@
53
53
  "iso-639-3": "^3.0.1",
54
54
  "usearch": "^2.21.4",
55
55
  "zod": "^4.3.6",
56
- "@xenova/transformers": "^2.17.2"
56
+ "@huggingface/transformers": "^4.2.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/bun": "^1.3.8",