@vellumai/assistant 0.10.5-dev.202607061137.28aab2f → 0.10.5-dev.202607061324.2dbc3d4

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.5-dev.202607061137.28aab2f",
3
+ "version": "0.10.5-dev.202607061324.2dbc3d4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -356,13 +356,6 @@ describe("delete paths route through the persistence-hook seam", () => {
356
356
  deletedBatches.push(ids);
357
357
  },
358
358
  async onAllConversationsCleared() {},
359
- onWorkerStartup() {},
360
- countMemoryBufferLines() {
361
- return 0;
362
- },
363
- hasPkbBufferContent() {
364
- return false;
365
- },
366
359
  };
367
360
 
368
361
  beforeEach(() => {
@@ -17,8 +17,6 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
17
17
 
18
18
  import { getHooksFor } from "../hooks/registry.js";
19
19
  import { RiskLevel } from "../permissions/types.js";
20
- import { guardPersistenceHooksByDisabledState } from "../plugins/defaults/memory/persistence-hooks-registration.js";
21
- import type { MessagePersistedEvent } from "../plugins/defaults/memory/persistence-lifecycle-seam.js";
22
20
  import {
23
21
  clearInjectorRegistry,
24
22
  getRegisteredInjectors,
@@ -258,109 +256,4 @@ describe("per-surface disabled-state filtering", () => {
258
256
  hooks = await getHooksFor("user-prompt-submit");
259
257
  expect(hooks).toHaveLength(0);
260
258
  });
261
-
262
- test("persistence hooks no-op while the memory plugin is disabled", async () => {
263
- let calls = 0;
264
- const bufferLines = 25;
265
- const guarded = guardPersistenceHooksByDisabledState("default-memory", {
266
- onMessagePersisted() {
267
- calls++;
268
- },
269
- onConversationForked() {},
270
- onConversationWiped() {
271
- return 0;
272
- },
273
- onConversationDeleted() {},
274
- onMessagesDeleted() {},
275
- async onAllConversationsCleared() {},
276
- onWorkerStartup() {},
277
- countMemoryBufferLines() {
278
- return bufferLines;
279
- },
280
- hasPkbBufferContent() {
281
- return true;
282
- },
283
- });
284
- const event: MessagePersistedEvent = {
285
- messageId: "msg-1",
286
- conversationId: "conv-1",
287
- role: "user",
288
- content: "[]",
289
- createdAt: 0,
290
- };
291
-
292
- // Enabled: the wrapped handler runs.
293
- await guarded.onMessagePersisted(event);
294
- expect(calls).toBe(1);
295
- // The gated queries return the real buffer state while enabled.
296
- expect(guarded.countMemoryBufferLines()).toBe(25);
297
- expect(guarded.hasPkbBufferContent()).toBe(true);
298
-
299
- // Disable via sentinel — checked at call time, no restart needed.
300
- await createSentinel("default-memory");
301
- await guarded.onMessagePersisted(event);
302
- expect(calls).toBe(1);
303
- // Gated: reports empty buffers while disabled, so scheduled consolidation
304
- // and filing stay inert.
305
- expect(guarded.countMemoryBufferLines()).toBe(0);
306
- expect(guarded.hasPkbBufferContent()).toBe(false);
307
-
308
- // Re-enable.
309
- await removeSentinel("default-memory");
310
- await guarded.onMessagePersisted(event);
311
- expect(calls).toBe(2);
312
- expect(guarded.countMemoryBufferLines()).toBe(25);
313
- expect(guarded.hasPkbBufferContent()).toBe(true);
314
- });
315
-
316
- test("cleanup persistence hooks run even while the memory plugin is disabled", async () => {
317
- let wiped = 0;
318
- let swept = 0;
319
- const deleted: string[][] = [];
320
- const convDeleted: string[] = [];
321
- let cleared = 0;
322
- const guarded = guardPersistenceHooksByDisabledState("default-memory", {
323
- onMessagePersisted() {},
324
- onConversationForked() {},
325
- onConversationWiped() {
326
- wiped++;
327
- return 7;
328
- },
329
- onConversationDeleted(id) {
330
- convDeleted.push(id);
331
- },
332
- onMessagesDeleted(ids) {
333
- deleted.push(ids);
334
- },
335
- async onAllConversationsCleared() {
336
- cleared++;
337
- },
338
- onWorkerStartup() {
339
- swept++;
340
- },
341
- countMemoryBufferLines() {
342
- return 0;
343
- },
344
- hasPkbBufferContent() {
345
- return false;
346
- },
347
- });
348
-
349
- await createSentinel("default-memory");
350
-
351
- // Cleanup hooks are NOT gated: they must still run (and return real values)
352
- // while disabled, or state created while enabled would be orphaned.
353
- expect(guarded.onConversationWiped("conv-1")).toBe(7);
354
- guarded.onConversationDeleted("conv-9");
355
- guarded.onMessagesDeleted(["msg-1", "msg-2"]);
356
- await guarded.onAllConversationsCleared();
357
- guarded.onWorkerStartup();
358
- expect(wiped).toBe(1);
359
- expect(convDeleted).toEqual(["conv-9"]);
360
- expect(deleted).toEqual([["msg-1", "msg-2"]]);
361
- expect(cleared).toBe(1);
362
- expect(swept).toBe(1);
363
-
364
- await removeSentinel("default-memory");
365
- });
366
259
  });
@@ -218,7 +218,6 @@ const BASELINE: Record<string, readonly string[]> = {
218
218
  "../../../util/sqlite-retry.js",
219
219
  "../../../util/strip-comment-lines.js",
220
220
  "../../../util/worker-memory.js",
221
- "../../disabled-state.js",
222
221
  "../../types.js",
223
222
  "../injection-presence.js",
224
223
  "../injector-order.js",
@@ -71,15 +71,6 @@ const { enqueueMemoryJob } =
71
71
  await import("../../../../persistence/jobs-store.js");
72
72
  const { GRAPH_MAINTENANCE_CHECKPOINTS, maybeEnqueueGraphMaintenanceJobs } =
73
73
  await import("../jobs-worker.js");
74
- const { registerMemoryPersistenceHooks } =
75
- await import("../persistence-lifecycle-seam.js");
76
- const { memoryPersistenceHooks } = await import("../persistence-hooks.js");
77
-
78
- // The scheduler reads the PKB buffer state through the persistence seam;
79
- // register the real memory implementation so `hasPkbBufferContent` reflects
80
- // the buffer files these tests write.
81
- registerMemoryPersistenceHooks(memoryPersistenceHooks);
82
-
83
74
  const FILING_KEY = GRAPH_MAINTENANCE_CHECKPOINTS.pkbFiling;
84
75
  const COMPACTION_KEY = GRAPH_MAINTENANCE_CHECKPOINTS.pkbCompaction;
85
76
 
@@ -57,7 +57,6 @@ afterAll(() => {
57
57
  process.env.VELLUM_WORKSPACE_DIR = previousWorkspaceEnv;
58
58
  }
59
59
  rmSync(tmpWorkspace, { recursive: true, force: true });
60
- resetMemoryPersistenceHooksForTests();
61
60
  });
62
61
 
63
62
  const { getMemoryDb } =
@@ -70,15 +69,6 @@ const { applyNestedDefaults } = await import("../../../../config/loader.js");
70
69
  const { getMemoryCheckpoint, setMemoryCheckpoint, deleteMemoryCheckpoint } =
71
70
  await import("../../../../persistence/checkpoints.js");
72
71
  const { maybeEnqueueGraphMaintenanceJobs } = await import("../jobs-worker.js");
73
- const { registerMemoryPersistenceHooks, resetMemoryPersistenceHooksForTests } =
74
- await import("../persistence-lifecycle-seam.js");
75
- const { memoryPersistenceHooks } = await import("../persistence-hooks.js");
76
-
77
- // The scheduler reads the memory buffer's line count through the persistence
78
- // seam; register the real memory implementation so `countMemoryBufferLines`
79
- // reflects the buffer files these tests write.
80
- registerMemoryPersistenceHooks(memoryPersistenceHooks);
81
-
82
72
  const CONSOLIDATE_CHECKPOINT_KEY = "memory_v2_consolidate_last_run";
83
73
 
84
74
  function buildConfig(overrides: {
@@ -26,13 +26,6 @@ const baseHooks: MemoryPersistenceHooks = {
26
26
  onConversationDeleted() {},
27
27
  onMessagesDeleted() {},
28
28
  async onAllConversationsCleared() {},
29
- onWorkerStartup() {},
30
- countMemoryBufferLines() {
31
- return 0;
32
- },
33
- hasPkbBufferContent() {
34
- return false;
35
- },
36
29
  };
37
30
 
38
31
  describe("memory persistence-lifecycle seam", () => {
@@ -43,18 +36,6 @@ describe("memory persistence-lifecycle seam", () => {
43
36
  await getMemoryPersistenceHooks().onMessagePersisted(event);
44
37
  });
45
38
 
46
- test("countMemoryBufferLines defaults to 0 when memory is not present", () => {
47
- expect(getMemoryPersistenceHooks().countMemoryBufferLines()).toBe(0);
48
- });
49
-
50
- test("countMemoryBufferLines returns the registered implementation's count", () => {
51
- registerMemoryPersistenceHooks({
52
- ...baseHooks,
53
- countMemoryBufferLines: () => 42,
54
- });
55
- expect(getMemoryPersistenceHooks().countMemoryBufferLines()).toBe(42);
56
- });
57
-
58
39
  test("getMemoryPersistenceHooks returns the registered implementation", async () => {
59
40
  const seen: MessagePersistedEvent[] = [];
60
41
  registerMemoryPersistenceHooks({
@@ -1,3 +1,5 @@
1
+ import { join } from "node:path";
2
+
1
3
  import { getConfig } from "../../../config/loader.js";
2
4
  import { isMemoryV3Live } from "../../../config/memory-v3-gate.js";
3
5
  import type { AssistantConfig } from "../../../config/types.js";
@@ -49,7 +51,10 @@ import {
49
51
  } from "../../../persistence/jobs-store.js";
50
52
  import { spawnMemoryWorkerProcess } from "../../../persistence/worker-control.js";
51
53
  import { getLogger } from "../../../util/logger.js";
52
- import { getMemoryPersistenceHooks } from "./persistence-lifecycle-seam.js";
54
+ import { getWorkspaceDir } from "../../../util/platform.js";
55
+ import { sweepOrphanMemoryRetrospectiveConversations } from "./memory-retrospective-startup-cleanup.js";
56
+ import { hasPkbBufferContent } from "./pkb-schedule.js";
57
+ import { countBufferLines } from "./v2/consolidation-job.js";
53
58
 
54
59
  const log = getLogger("memory-jobs-worker");
55
60
 
@@ -240,7 +245,7 @@ export function startInProcessMemoryJobsWorker(
240
245
  // left behind by daemon crashes mid-job. Best-effort — never block worker
241
246
  // startup on cleanup failures.
242
247
  try {
243
- getMemoryPersistenceHooks().onWorkerStartup();
248
+ sweepOrphanMemoryRetrospectiveConversations();
244
249
  } catch (err) {
245
250
  log.warn(
246
251
  { err },
@@ -773,6 +778,11 @@ function isWithinPkbActiveHours(
773
778
  return hour >= start || hour < end;
774
779
  }
775
780
 
781
+ /** Line count of the memory buffer, the scheduler's consolidation gate. */
782
+ function memoryBufferLineCount(): number {
783
+ return countBufferLines(join(getWorkspaceDir(), "memory", "buffer.md"));
784
+ }
785
+
776
786
  export function maybeEnqueueGraphMaintenanceJobs(
777
787
  config: AssistantConfig,
778
788
  nowMs = Date.now(),
@@ -846,10 +856,7 @@ export function maybeEnqueueGraphMaintenanceJobs(
846
856
  // The checkpoint advances so the next check fires after the regular
847
857
  // interval. Manual "Run now" is unaffected (routes layer, not schedule).
848
858
  if (jobType === consolidateEntry.jobType) {
849
- if (
850
- getMemoryPersistenceHooks().countMemoryBufferLines() <
851
- MIN_BUFFER_LINES_FOR_CONSOLIDATION
852
- ) {
859
+ if (memoryBufferLineCount() < MIN_BUFFER_LINES_FOR_CONSOLIDATION) {
853
860
  log.debug(
854
861
  "Scheduled consolidation skipped: buffer under minimum line threshold",
855
862
  );
@@ -884,7 +891,7 @@ export function maybeEnqueueGraphMaintenanceJobs(
884
891
  maxLines !== null &&
885
892
  !hasActiveJobOfType(consolidateEntry.jobType)
886
893
  ) {
887
- if (getMemoryPersistenceHooks().countMemoryBufferLines() >= maxLines) {
894
+ if (memoryBufferLineCount() >= maxLines) {
888
895
  enqueueMemoryJob(
889
896
  consolidateEntry.jobType,
890
897
  AUTOMATIC_CONSOLIDATION_JOB_PAYLOAD,
@@ -926,7 +933,7 @@ export function maybeEnqueueGraphMaintenanceJobs(
926
933
  intervalMs: filingConfig.intervalMs,
927
934
  jobType: "pkb_filing",
928
935
  enabled: filingConfig.enabled,
929
- hasWork: () => getMemoryPersistenceHooks().hasPkbBufferContent(),
936
+ hasWork: () => hasPkbBufferContent(),
930
937
  },
931
938
  {
932
939
  key: GRAPH_MAINTENANCE_CHECKPOINTS.pkbCompaction,
@@ -1,95 +1,23 @@
1
- import { isPluginDisabled } from "../../disabled-state.js";
2
- import memoryPkg from "./package.json" with { type: "json" };
3
1
  import { memoryPersistenceHooks } from "./persistence-hooks.js";
4
- import {
5
- type MemoryPersistenceHooks,
6
- registerMemoryPersistenceHooks,
7
- } from "./persistence-lifecycle-seam.js";
2
+ import { registerMemoryPersistenceHooks } from "./persistence-lifecycle-seam.js";
8
3
 
9
4
  /**
10
5
  * Registration entry point for the memory plugin's persistence-lifecycle seam
11
6
  * (`persistence-lifecycle-seam.ts`): wires the plugin's handler implementation
12
- * (`persistence-hooks.ts`) into the seam's slot, wrapped in the disabled-state
13
- * guard.
7
+ * (`persistence-hooks.ts`) into the seam's slot.
14
8
  *
15
9
  * Kept separate from the seam module on purpose: this module transitively
16
10
  * imports persistence (through the handler implementation) while persistence
17
11
  * imports the seam, so folding the two together would close an import cycle.
18
12
  */
19
13
 
20
- /**
21
- * Wrap the plugin's persistence hooks so its ACTIVE side-effect hooks no-op while
22
- * the plugin is disabled (`assistant plugins disable <name>`), mirroring the
23
- * read-time disabled-state filtering the injector/hook/job-handler/tool surfaces
24
- * apply. The sentinel is checked per call, so enable/disable takes effect on the
25
- * next write without a daemon restart. CLEANUP hooks (`onConversationWiped`,
26
- * `onConversationDeleted`, `onMessagesDeleted`, `onAllConversationsCleared`,
27
- * `onWorkerStartup`) are intentionally NOT gated — they must run even when the
28
- * plugin is disabled so state created while it was enabled is not orphaned.
29
- */
30
- export function guardPersistenceHooksByDisabledState(
31
- pluginName: string,
32
- hooks: MemoryPersistenceHooks,
33
- ): MemoryPersistenceHooks {
34
- return {
35
- onMessagePersisted(event) {
36
- if (isPluginDisabled(pluginName)) return;
37
- return hooks.onMessagePersisted(event);
38
- },
39
- onConversationForked(event) {
40
- if (isPluginDisabled(pluginName)) return;
41
- return hooks.onConversationForked(event);
42
- },
43
- // Gated like the active side effects above: a disabled plugin reports an
44
- // empty buffer, so the maintenance scheduler treats it as "no buffered
45
- // work" and skips consolidation — matching how disabled injectors/hooks go
46
- // inert.
47
- countMemoryBufferLines() {
48
- if (isPluginDisabled(pluginName)) return 0;
49
- return hooks.countMemoryBufferLines();
50
- },
51
- // Gated the same way: a disabled plugin reports an empty PKB buffer, so
52
- // the maintenance scheduler skips scheduled filing.
53
- hasPkbBufferContent() {
54
- if (isPluginDisabled(pluginName)) return false;
55
- return hooks.hasPkbBufferContent();
56
- },
57
- // Cleanup hooks are NOT gated on disabled-state: they must run even while
58
- // the plugin is disabled, or jobs/conversations created while it was
59
- // enabled would be orphaned.
60
- onConversationWiped(conversationId) {
61
- return hooks.onConversationWiped(conversationId);
62
- },
63
- onConversationDeleted(conversationId) {
64
- return hooks.onConversationDeleted(conversationId);
65
- },
66
- onMessagesDeleted(messageIds) {
67
- return hooks.onMessagesDeleted(messageIds);
68
- },
69
- onAllConversationsCleared() {
70
- return hooks.onAllConversationsCleared();
71
- },
72
- onWorkerStartup() {
73
- return hooks.onWorkerStartup();
74
- },
75
- };
76
- }
77
-
78
14
  /**
79
15
  * Install the memory feature's persistence-lifecycle handlers into the seam.
80
16
  * `bootstrapPlugins` calls this before the per-plugin init loop so the seam is
81
17
  * wired up front (the standalone memory jobs worker, which has no plugin
82
- * bootstrap, calls it directly); the handlers are guarded by
83
- * {@link guardPersistenceHooksByDisabledState} so a disabled memory plugin
84
- * drives no persistence side effects and re-enabling it takes effect on the
85
- * next write. The seam holds a single handler set, so this replaces any prior
86
- * registration.
18
+ * bootstrap, calls it directly). The seam holds a single handler set, so this
19
+ * replaces any prior registration.
87
20
  */
88
21
  export function registerDefaultPluginPersistenceHooks(): void {
89
- registerMemoryPersistenceHooks(
90
- guardPersistenceHooksByDisabledState(
91
- memoryPkg.name,
92
- memoryPersistenceHooks,
93
- ),
94
- );
22
+ registerMemoryPersistenceHooks(memoryPersistenceHooks);
95
23
  }
@@ -1,29 +1,23 @@
1
- import { join } from "node:path";
2
-
3
1
  import { getConfig } from "../../../config/loader.js";
4
2
  import {
5
3
  clearMessagesLexicalIndex,
6
4
  enqueueDeleteMessageLexical,
7
5
  enqueuePurgeConversationLexical,
8
6
  } from "../../../persistence/job-handlers/message-lexical.js";
9
- import { getWorkspaceDir } from "../../../util/platform.js";
10
7
  import { getMemoryConfig } from "./config.js";
11
8
  import { forkGraphMemoryState } from "./graph/graph-memory-state-store.js";
12
9
  import { indexMessageNow } from "./indexer.js";
13
- import { sweepOrphanMemoryRetrospectiveConversations } from "./memory-retrospective-startup-cleanup.js";
14
10
  import { forkRetrospectiveState } from "./memory-retrospective-state.js";
15
11
  import type {
16
12
  ConversationForkedEvent,
17
13
  MemoryPersistenceHooks,
18
14
  MessagePersistedEvent,
19
15
  } from "./persistence-lifecycle-seam.js";
20
- import { hasPkbBufferContent as pkbBufferHasContent } from "./pkb-schedule.js";
21
16
  import { cancelPendingJobsForConversation } from "./task-memory-cleanup.js";
22
17
  import {
23
18
  forkActivationState,
24
19
  seedForkActivationState,
25
20
  } from "./v2/activation-store.js";
26
- import { countBufferLines } from "./v2/consolidation-job.js";
27
21
  import {
28
22
  extractInjectedConceptSlugs,
29
23
  readInjectedBlock,
@@ -154,16 +148,4 @@ export const memoryPersistenceHooks: MemoryPersistenceHooks = {
154
148
  // be erased when the drop lands.
155
149
  await clearMessagesLexicalIndex(getConfig());
156
150
  },
157
-
158
- onWorkerStartup(): void {
159
- sweepOrphanMemoryRetrospectiveConversations();
160
- },
161
-
162
- countMemoryBufferLines(): number {
163
- return countBufferLines(join(getWorkspaceDir(), "memory", "buffer.md"));
164
- },
165
-
166
- hasPkbBufferContent(): boolean {
167
- return pkbBufferHasContent();
168
- },
169
151
  };
@@ -134,31 +134,6 @@ export interface MemoryPersistenceHooks {
134
134
  * collection that is about to be dropped).
135
135
  */
136
136
  onAllConversationsCleared(): Promise<void>;
137
-
138
- /**
139
- * The background-job worker is starting. The memory feature sweeps orphan
140
- * retrospective conversations left by a crash mid-job. Best-effort; the caller
141
- * wraps it in try/catch. Cleanup — runs even while the plugin is disabled.
142
- */
143
- onWorkerStartup(): void;
144
-
145
- /**
146
- * Current line count of the memory buffer (`memory/buffer.md`). The
147
- * maintenance scheduler reads it to gate scheduled consolidation — skip a
148
- * scheduled run below a floor, force one above a ceiling. Returns 0 when
149
- * memory is not present, which the scheduler reads as "no buffered work" and
150
- * therefore no consolidation.
151
- */
152
- countMemoryBufferLines(): number;
153
-
154
- /**
155
- * Whether the PKB buffer (`pkb/buffer.md`) has any filable content. The
156
- * maintenance scheduler reads it to gate the scheduled `pkb_filing` job —
157
- * an empty buffer means no work, so no LLM run. Returns false when memory
158
- * is not present, which the scheduler reads as "no buffered work" and
159
- * therefore no filing.
160
- */
161
- hasPkbBufferContent(): boolean;
162
137
  }
163
138
 
164
139
  const NOOP: MemoryPersistenceHooks = {
@@ -170,13 +145,6 @@ const NOOP: MemoryPersistenceHooks = {
170
145
  onConversationDeleted() {},
171
146
  onMessagesDeleted() {},
172
147
  async onAllConversationsCleared() {},
173
- onWorkerStartup() {},
174
- countMemoryBufferLines() {
175
- return 0;
176
- },
177
- hasPkbBufferContent() {
178
- return false;
179
- },
180
148
  };
181
149
 
182
150
  let current: MemoryPersistenceHooks = NOOP;