@vellumai/assistant 0.10.3-dev.202606272129.e87b552 → 0.10.3-dev.202606272229.2ffe9f3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.10.3-dev.202606272129.e87b552",
3
+ "version": "0.10.3-dev.202606272229.2ffe9f3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Regression: `persistUserMessage` calls `ctx.setProcessing(true)`, which
3
+ * persists the flag to the DB and can throw (e.g. SQLITE_BUSY). That call must
4
+ * run inside the function's try/catch so a failure unwinds the request-id and
5
+ * abort-controller bookkeeping instead of stranding it on the conversation.
6
+ * A later failure while clearing the flag must not mask the original error.
7
+ */
8
+ import { describe, expect, test } from "bun:test";
9
+
10
+ import type { MessagingConversationContext } from "../daemon/conversation-messaging.js";
11
+ import { persistUserMessage } from "../daemon/conversation-messaging.js";
12
+
13
+ type SetProcessingBehavior = (value: boolean) => void;
14
+
15
+ function makeContext(
16
+ setProcessing: SetProcessingBehavior,
17
+ ): MessagingConversationContext & {
18
+ processing: boolean;
19
+ } {
20
+ let processing = false;
21
+ const ctx = {
22
+ conversationId: "conv-set-processing-failure",
23
+ messages: [],
24
+ abortController: null,
25
+ currentRequestId: undefined,
26
+ queue: {} as never,
27
+ get processing() {
28
+ return processing;
29
+ },
30
+ isProcessing: () => processing,
31
+ setProcessing: (value: boolean) => {
32
+ setProcessing(value);
33
+ processing = value;
34
+ },
35
+ getTurnChannelContext: () => null,
36
+ getTurnInterfaceContext: () => null,
37
+ } as unknown as MessagingConversationContext & { processing: boolean };
38
+ return ctx;
39
+ }
40
+
41
+ describe("persistUserMessage setProcessing failure", () => {
42
+ test("clears request-id and abort bookkeeping when setProcessing(true) throws", async () => {
43
+ const ctx = makeContext((value) => {
44
+ if (value) throw new Error("database is locked (SQLITE_BUSY)");
45
+ });
46
+
47
+ await expect(
48
+ persistUserMessage(ctx, { content: "hello", requestId: "req-1" }),
49
+ ).rejects.toThrow("database is locked");
50
+
51
+ expect(ctx.currentRequestId).toBeUndefined();
52
+ expect(ctx.abortController).toBeNull();
53
+ expect(ctx.isProcessing()).toBe(false);
54
+ });
55
+
56
+ test("a failing clear does not mask the original persist error", async () => {
57
+ // setProcessing throws on both the set and the clear; the original error
58
+ // must still propagate and the bookkeeping must still be reset.
59
+ const ctx = makeContext(() => {
60
+ throw new Error("database is locked (SQLITE_BUSY)");
61
+ });
62
+
63
+ await expect(
64
+ persistUserMessage(ctx, { content: "hello", requestId: "req-2" }),
65
+ ).rejects.toThrow("database is locked");
66
+
67
+ expect(ctx.currentRequestId).toBeUndefined();
68
+ expect(ctx.abortController).toBeNull();
69
+ });
70
+ });
@@ -412,10 +412,13 @@ export async function persistUserMessage(
412
412
 
413
413
  const reqId = options.requestId ?? uuid();
414
414
  ctx.currentRequestId = reqId;
415
- ctx.setProcessing(true);
416
415
  ctx.abortController = new AbortController();
417
416
 
418
417
  try {
418
+ // `setProcessing(true)` persists the flag and can throw (e.g.
419
+ // SQLITE_BUSY). Keeping it inside the try ensures a failure here unwinds
420
+ // the request-id/abort bookkeeping below rather than stranding it.
421
+ ctx.setProcessing(true);
419
422
  const result = await persistQueuedMessageBody(ctx, {
420
423
  ...options,
421
424
  attachments,
@@ -428,7 +431,18 @@ export async function persistUserMessage(
428
431
  }
429
432
  return result;
430
433
  } catch (err) {
431
- ctx.setProcessing(false);
434
+ // Clear the flag, but never let a clear failure mask the original error
435
+ // or skip the bookkeeping reset. `setProcessing` reverts its own
436
+ // in-memory flag when its persist throws, so the conversation is left
437
+ // consistent either way.
438
+ try {
439
+ ctx.setProcessing(false);
440
+ } catch (clearErr) {
441
+ log.error(
442
+ { err: clearErr, conversationId: ctx.conversationId },
443
+ "Failed to clear processing flag after persistUserMessage failure",
444
+ );
445
+ }
432
446
  ctx.abortController = null;
433
447
  ctx.currentRequestId = undefined;
434
448
  throw err;