@vellumai/assistant 0.10.3-dev.202606271631.97b63cd → 0.10.3-dev.202606271833.9783ef3
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 +1 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/migrations.ts +25 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +14 -0
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +17 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +31 -0
- package/src/memory/migrations/__tests__/222-strip-placeholder-sentinels-from-messages.test.ts +31 -0
package/package.json
CHANGED
package/src/config/schema.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "./schemas/logging.js";
|
|
43
43
|
import { McpConfigSchema } from "./schemas/mcp.js";
|
|
44
44
|
import { MemoryConfigSchema } from "./schemas/memory.js";
|
|
45
|
+
import { MigrationsConfigSchema } from "./schemas/migrations.js";
|
|
45
46
|
import { NotificationsConfigSchema } from "./schemas/notifications.js";
|
|
46
47
|
import {
|
|
47
48
|
DaemonConfigSchema,
|
|
@@ -63,6 +64,7 @@ export const AssistantConfigSchema = z
|
|
|
63
64
|
.object({
|
|
64
65
|
services: ServicesSchema.default(ServicesSchema.parse({})),
|
|
65
66
|
memory: MemoryConfigSchema.default(MemoryConfigSchema.parse({})),
|
|
67
|
+
migrations: MigrationsConfigSchema.default(MigrationsConfigSchema.parse({})),
|
|
66
68
|
dataDir: z
|
|
67
69
|
.string({ error: "dataDir must be a string" })
|
|
68
70
|
.default(getDataDir())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const MigrationsWorkerConfigSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
enabled: z
|
|
6
|
+
.boolean({ error: "migrations.worker.enabled must be a boolean" })
|
|
7
|
+
.default(false)
|
|
8
|
+
.describe(
|
|
9
|
+
"Enable the async migration worker. While false, migrations that are too expensive to run inside the synchronous startup migration runner skip their work and pass, deferring until the async runner is in place.",
|
|
10
|
+
),
|
|
11
|
+
})
|
|
12
|
+
.describe("Async migration worker configuration");
|
|
13
|
+
|
|
14
|
+
export const MigrationsConfigSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
worker: MigrationsWorkerConfigSchema.default(
|
|
17
|
+
MigrationsWorkerConfigSchema.parse({}),
|
|
18
|
+
),
|
|
19
|
+
})
|
|
20
|
+
.describe("Workspace/database migration configuration");
|
|
21
|
+
|
|
22
|
+
export type MigrationsWorkerConfig = z.infer<
|
|
23
|
+
typeof MigrationsWorkerConfigSchema
|
|
24
|
+
>;
|
|
25
|
+
export type MigrationsConfig = z.infer<typeof MigrationsConfigSchema>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getConfig } from "../../config/loader.js";
|
|
1
2
|
import { getLogger } from "../../util/logger.js";
|
|
2
3
|
import { getDbPath } from "../../util/platform.js";
|
|
3
4
|
import { runAsyncSqlite } from "../db-async-query.js";
|
|
@@ -124,6 +125,19 @@ function windowSql(lo: number, hi: number): string {
|
|
|
124
125
|
export async function migrateStripThinkingFromConsolidated(
|
|
125
126
|
database: DrizzleDb,
|
|
126
127
|
): Promise<void> {
|
|
128
|
+
// Gated on the async migration worker. This sweep proved too expensive for
|
|
129
|
+
// the synchronous startup migration runner — on a large `messages` table it
|
|
130
|
+
// can hold the daemon for minutes and trip the startup probe into a restart
|
|
131
|
+
// loop. While `migrations.worker.enabled` is false, skip the work and let the
|
|
132
|
+
// step pass so startup is never blocked; performing the sweep is deferred to
|
|
133
|
+
// the new async migration runner once it takes shape.
|
|
134
|
+
if (!getConfig().migrations.worker.enabled) {
|
|
135
|
+
log.info(
|
|
136
|
+
"Migration 209: skipped — migrations.worker.enabled is false; deferring the strip-thinking sweep to the async migration runner",
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
127
141
|
const raw = getSqliteFrom(database);
|
|
128
142
|
const dbPath = getDbPath();
|
|
129
143
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { getConfig } from "../../config/loader.js";
|
|
1
2
|
import {
|
|
2
3
|
PLACEHOLDER_BLOCKS_OMITTED,
|
|
3
4
|
PLACEHOLDER_EMPTY_TURN,
|
|
4
5
|
} from "../../providers/placeholder-sentinels.js";
|
|
6
|
+
import { getLogger } from "../../util/logger.js";
|
|
5
7
|
import { getDbPath } from "../../util/platform.js";
|
|
6
8
|
import { runAsyncSqlite } from "../db-async-query.js";
|
|
7
9
|
import type { DrizzleDb } from "../db-connection.js";
|
|
8
10
|
import { getSqliteFrom } from "../db-connection.js";
|
|
9
11
|
|
|
12
|
+
const log = getLogger("db-init");
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* Number of `rowid` values swept per `runAsyncSqlite` dispatch. Each window is
|
|
12
16
|
* one off-thread subprocess transaction. Keeping it well below the row count of
|
|
@@ -123,6 +127,19 @@ function windowSql(lo: number, hi: number): string {
|
|
|
123
127
|
export async function migrateStripPlaceholderSentinelsFromMessages(
|
|
124
128
|
database: DrizzleDb,
|
|
125
129
|
): Promise<void> {
|
|
130
|
+
// Gated on the async migration worker. This full-table sweep proved too
|
|
131
|
+
// expensive for the synchronous startup migration runner — on a large
|
|
132
|
+
// `messages` table it can hold the daemon's event loop and write lock for
|
|
133
|
+
// minutes. While `migrations.worker.enabled` is false, skip the work and let
|
|
134
|
+
// the step pass so startup is never blocked; performing the sweep is deferred
|
|
135
|
+
// to the new async migration runner once it takes shape.
|
|
136
|
+
if (!getConfig().migrations.worker.enabled) {
|
|
137
|
+
log.info(
|
|
138
|
+
"Migration 222: skipped — migrations.worker.enabled is false; deferring the placeholder-sentinel sweep to the async migration runner",
|
|
139
|
+
);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
126
143
|
const raw = getSqliteFrom(database);
|
|
127
144
|
const dbPath = getDbPath();
|
|
128
145
|
|
|
@@ -17,9 +17,18 @@ const {
|
|
|
17
17
|
ROWID_WINDOW,
|
|
18
18
|
WINDOW_TIMEOUT_MS,
|
|
19
19
|
} = await import("../209-strip-thinking-from-consolidated.js");
|
|
20
|
+
const { loadRawConfig, saveRawConfig } =
|
|
21
|
+
await import("../../../config/loader.js");
|
|
20
22
|
|
|
21
23
|
await initializeDb();
|
|
22
24
|
|
|
25
|
+
// The sweep is gated behind `migrations.worker.enabled` (default false), under
|
|
26
|
+
// which it short-circuits and passes. Enable it so the migration does its work
|
|
27
|
+
// for these tests.
|
|
28
|
+
const rawConfig = loadRawConfig();
|
|
29
|
+
rawConfig.migrations = { worker: { enabled: true } };
|
|
30
|
+
saveRawConfig(rawConfig);
|
|
31
|
+
|
|
23
32
|
const CONV = "conv-209";
|
|
24
33
|
getSqlite()
|
|
25
34
|
.query(
|
|
@@ -283,6 +292,28 @@ describe("migration 209 — strip thinking from consolidated assistant messages"
|
|
|
283
292
|
expect(watermark()).toBeNull();
|
|
284
293
|
});
|
|
285
294
|
|
|
295
|
+
test("skips the sweep and passes when migrations.worker.enabled is false", async () => {
|
|
296
|
+
const original = JSON.stringify([
|
|
297
|
+
{ type: "thinking", thinking: "secret", signature: "s" },
|
|
298
|
+
{ type: "text", text: "gated" },
|
|
299
|
+
]);
|
|
300
|
+
const { id } = insert("assistant", original);
|
|
301
|
+
|
|
302
|
+
const disabled = loadRawConfig();
|
|
303
|
+
disabled.migrations = { worker: { enabled: false } };
|
|
304
|
+
saveRawConfig(disabled);
|
|
305
|
+
try {
|
|
306
|
+
// Resolves without throwing and leaves the row untouched — the work is
|
|
307
|
+
// deferred to the async migration runner.
|
|
308
|
+
await migrateStripThinkingFromConsolidated(getDb());
|
|
309
|
+
expect(content(id)).toBe(original);
|
|
310
|
+
} finally {
|
|
311
|
+
const enabled = loadRawConfig();
|
|
312
|
+
enabled.migrations = { worker: { enabled: true } };
|
|
313
|
+
saveRawConfig(enabled);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
286
317
|
test("keeps the sweep window and timeout bounded so it cannot overrun a whole table", () => {
|
|
287
318
|
// Regression guard: ROWID_WINDOW was 100_000, larger than a typical
|
|
288
319
|
// messages table, so the entire table swept in one subprocess that overran
|
package/src/memory/migrations/__tests__/222-strip-placeholder-sentinels-from-messages.test.ts
CHANGED
|
@@ -20,9 +20,18 @@ const {
|
|
|
20
20
|
ROWID_WINDOW,
|
|
21
21
|
WINDOW_TIMEOUT_MS,
|
|
22
22
|
} = await import("../222-strip-placeholder-sentinels-from-messages.js");
|
|
23
|
+
const { loadRawConfig, saveRawConfig } =
|
|
24
|
+
await import("../../../config/loader.js");
|
|
23
25
|
|
|
24
26
|
await initializeDb();
|
|
25
27
|
|
|
28
|
+
// The sweep is gated behind `migrations.worker.enabled` (default false), under
|
|
29
|
+
// which it short-circuits and passes. Enable it so the migration does its work
|
|
30
|
+
// for these tests.
|
|
31
|
+
const rawConfig = loadRawConfig();
|
|
32
|
+
rawConfig.migrations = { worker: { enabled: true } };
|
|
33
|
+
saveRawConfig(rawConfig);
|
|
34
|
+
|
|
26
35
|
const CONV = "conv-222";
|
|
27
36
|
getSqlite()
|
|
28
37
|
.query(
|
|
@@ -235,6 +244,28 @@ describe("migration 222 — strip placeholder sentinels from assistant messages"
|
|
|
235
244
|
expect(blocks(second.id)).toEqual([{ type: "text", text: "second" }]);
|
|
236
245
|
});
|
|
237
246
|
|
|
247
|
+
test("skips the sweep and passes when migrations.worker.enabled is false", async () => {
|
|
248
|
+
const original = JSON.stringify([
|
|
249
|
+
{ type: "text", text: PLACEHOLDER_EMPTY_TURN },
|
|
250
|
+
{ type: "text", text: "gated" },
|
|
251
|
+
]);
|
|
252
|
+
const { id } = insert("assistant", original);
|
|
253
|
+
|
|
254
|
+
const disabled = loadRawConfig();
|
|
255
|
+
disabled.migrations = { worker: { enabled: false } };
|
|
256
|
+
saveRawConfig(disabled);
|
|
257
|
+
try {
|
|
258
|
+
// Resolves without throwing and leaves the row untouched — the work is
|
|
259
|
+
// deferred to the async migration runner.
|
|
260
|
+
await migrateStripPlaceholderSentinelsFromMessages(getDb());
|
|
261
|
+
expect(content(id)).toBe(original);
|
|
262
|
+
} finally {
|
|
263
|
+
const enabled = loadRawConfig();
|
|
264
|
+
enabled.migrations = { worker: { enabled: true } };
|
|
265
|
+
saveRawConfig(enabled);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
238
269
|
test("keeps the sweep window and timeout bounded so it cannot overrun a whole table", () => {
|
|
239
270
|
expect(ROWID_WINDOW).toBeLessThanOrEqual(10_000);
|
|
240
271
|
expect(WINDOW_TIMEOUT_MS).toBeLessThan(60 * 60 * 1000);
|