@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
|
@@ -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
|
-
|
|
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;
|