@vellumai/assistant 0.7.3 → 0.8.0
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/ARCHITECTURE.md +29 -28
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/knip.json +1 -0
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/openapi.yaml +22 -4
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +12 -25
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -26
- package/src/__tests__/context-search-pkb-source.test.ts +12 -6
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -6
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +3 -3
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -6
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +15 -6
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +73 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +59 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -7
- package/src/__tests__/filing-service.test.ts +2 -19
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/injector-chain.test.ts +24 -16
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/relay-server.test.ts +46 -2
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +15 -27
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-decision-primitive.ts +0 -13
- package/src/approvals/guardian-request-resolvers.ts +4 -32
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/memory-v2.ts +7 -7
- package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -254
- package/src/cli/commands/oauth/connect.ts +10 -52
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/feature-flag-registry.json +1 -17
- package/src/config/loader.ts +72 -19
- package/src/config/schemas/memory-v2.ts +1 -1
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +32 -0
- package/src/daemon/conversation-agent-loop.ts +13 -10
- package/src/daemon/conversation-lifecycle.ts +22 -8
- package/src/daemon/conversation-surfaces.ts +16 -14
- package/src/daemon/conversation-tool-setup.ts +9 -5
- package/src/daemon/conversation.ts +1 -1
- package/src/daemon/handlers/shared.ts +26 -0
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +88 -73
- package/src/daemon/memory-v2-startup.ts +55 -14
- package/src/daemon/message-types/messages.ts +19 -1
- package/src/documents/document-store.ts +35 -1
- package/src/filing/filing-service.ts +2 -3
- package/src/heartbeat/heartbeat-service.ts +1 -1
- package/src/ipc/assistant-server.ts +93 -36
- package/src/ipc/skill-server.ts +99 -42
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +10 -57
- package/src/memory/context-search/sources/memory-v2.ts +1 -17
- package/src/memory/context-search/sources/memory.ts +2 -2
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +104 -61
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +32 -9
- package/src/memory/graph/graph-search.test.ts +6 -5
- package/src/memory/graph/graph-search.ts +3 -4
- package/src/memory/graph/retriever.test.ts +12 -7
- package/src/memory/graph/retriever.ts +4 -5
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +1 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-worker.ts +8 -4
- package/src/memory/pkb/pkb-search.test.ts +6 -5
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -0
- package/src/memory/search/semantic.ts +4 -5
- package/src/memory/v2/__tests__/activation.test.ts +35 -5
- package/src/memory/v2/__tests__/consolidation-job.test.ts +21 -32
- package/src/memory/v2/__tests__/injection.test.ts +140 -23
- package/src/memory/v2/__tests__/qdrant.test.ts +310 -9
- package/src/memory/v2/__tests__/sim.test.ts +118 -7
- package/src/memory/v2/__tests__/static-context.test.ts +1 -13
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/consolidation-job.ts +7 -8
- package/src/memory/v2/injection.ts +32 -12
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +5 -0
- package/src/memory/v2/qdrant.ts +209 -48
- package/src/memory/v2/sim.ts +67 -26
- package/src/memory/v2/static-context.ts +4 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +7 -0
- package/src/notifications/copy-composer.ts +46 -12
- package/src/notifications/decision-engine.ts +46 -0
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +1 -2
- package/src/proactive-artifact/job.test.ts +51 -4
- package/src/proactive-artifact/job.ts +16 -2
- package/src/proactive-artifact/message-copy.ts +18 -1
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/runtime/auth/route-policy.ts +1 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/guardian-reply-router.ts +0 -10
- package/src/runtime/pending-interactions.ts +19 -15
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/debug-bash-routes.ts +2 -0
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +0 -3
- package/src/runtime/routes/memory-item-routes.test.ts +3 -9
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +103 -17
- package/src/skills/include-graph.ts +35 -13
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/memory/register.test.ts +7 -5
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +19 -1
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +4 -62
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +6 -0
package/src/ipc/skill-server.ts
CHANGED
|
@@ -37,9 +37,13 @@
|
|
|
37
37
|
* back to a shorter deterministic path via the shared socket-path resolver.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
-
import { existsSync,
|
|
40
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
41
41
|
import { createServer, type Server, type Socket } from "node:net";
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
ensureSocketDir,
|
|
45
|
+
SocketWatchdog,
|
|
46
|
+
} from "@vellumai/ipc-server-utils";
|
|
43
47
|
|
|
44
48
|
import {
|
|
45
49
|
type SkillRouteHandle,
|
|
@@ -233,6 +237,15 @@ class SkillIpcConnectionState implements SkillIpcConnection {
|
|
|
233
237
|
// Server
|
|
234
238
|
// ---------------------------------------------------------------------------
|
|
235
239
|
|
|
240
|
+
/** Optional configuration for {@link SkillIpcServer}. */
|
|
241
|
+
export interface SkillIpcServerOptions {
|
|
242
|
+
/**
|
|
243
|
+
* How often the socket-file watchdog stats the listening socket path.
|
|
244
|
+
* Set to `0` to disable. Defaults to {@link SocketWatchdog}'s 5000ms.
|
|
245
|
+
*/
|
|
246
|
+
watchdogIntervalMs?: number;
|
|
247
|
+
}
|
|
248
|
+
|
|
236
249
|
export class SkillIpcServer {
|
|
237
250
|
private server: Server | null = null;
|
|
238
251
|
private clients = new Set<Socket>();
|
|
@@ -252,8 +265,15 @@ export class SkillIpcServer {
|
|
|
252
265
|
private connections = new WeakMap<Socket, SkillIpcConnectionState>();
|
|
253
266
|
private nextConnectionId = 1;
|
|
254
267
|
private socketPath: string;
|
|
268
|
+
private watchdog: SocketWatchdog;
|
|
269
|
+
/**
|
|
270
|
+
* Servers whose listener path has been replaced by a re-bind. Kept around
|
|
271
|
+
* so already-connected sockets continue to work; closed gracefully once
|
|
272
|
+
* their accept loops drain.
|
|
273
|
+
*/
|
|
274
|
+
private legacyServers = new Set<Server>();
|
|
255
275
|
|
|
256
|
-
constructor() {
|
|
276
|
+
constructor(options?: SkillIpcServerOptions) {
|
|
257
277
|
const resolution = resolveSkillIpcSocketPath();
|
|
258
278
|
this.socketPath = resolution.path;
|
|
259
279
|
log.info(
|
|
@@ -266,6 +286,21 @@ export class SkillIpcServer {
|
|
|
266
286
|
for (const route of skillIpcStreamingRoutes) {
|
|
267
287
|
this.streamingMethods.set(route.method, route.handler);
|
|
268
288
|
}
|
|
289
|
+
|
|
290
|
+
this.watchdog = new SocketWatchdog({
|
|
291
|
+
socketPath: this.socketPath,
|
|
292
|
+
intervalMs: options?.watchdogIntervalMs,
|
|
293
|
+
getServer: () => this.server,
|
|
294
|
+
createServer: () => this.createListeningServer(),
|
|
295
|
+
onRebind: (newServer, oldServer) => {
|
|
296
|
+
this.server = newServer;
|
|
297
|
+
this.legacyServers.add(oldServer);
|
|
298
|
+
oldServer.close(() => {
|
|
299
|
+
this.legacyServers.delete(oldServer);
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
log,
|
|
303
|
+
});
|
|
269
304
|
}
|
|
270
305
|
|
|
271
306
|
/** Register an additional method handler after construction. */
|
|
@@ -349,17 +384,71 @@ export class SkillIpcServer {
|
|
|
349
384
|
/** Start listening on the Unix domain socket. */
|
|
350
385
|
async start(): Promise<void> {
|
|
351
386
|
// Ensure the parent directory exists before listening.
|
|
352
|
-
|
|
353
|
-
if (!existsSync(socketDir)) {
|
|
354
|
-
mkdirSync(socketDir, { recursive: true, mode: 0o700 });
|
|
355
|
-
}
|
|
387
|
+
ensureSocketDir(this.socketPath);
|
|
356
388
|
|
|
357
389
|
// Probe before unlink so a second daemon can't silently orphan an active
|
|
358
390
|
// listener (Unix lets you unlink a still-bound socket file). See
|
|
359
391
|
// `ensureSocketPathFree` for the behavior matrix.
|
|
360
392
|
await ensureSocketPathFree(this.socketPath);
|
|
361
393
|
|
|
362
|
-
this.server =
|
|
394
|
+
this.server = this.createListeningServer();
|
|
395
|
+
this.server.listen(this.socketPath, () => {
|
|
396
|
+
log.info({ path: this.socketPath }, "Skill IPC server listening");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
this.watchdog.start();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Stop the server and disconnect all clients. */
|
|
403
|
+
stop(): void {
|
|
404
|
+
this.watchdog.stop();
|
|
405
|
+
|
|
406
|
+
for (const client of this.clients) {
|
|
407
|
+
this.teardownSubscriptions(client);
|
|
408
|
+
this.teardownConnection(client);
|
|
409
|
+
if (!client.destroyed) client.destroy();
|
|
410
|
+
}
|
|
411
|
+
this.clients.clear();
|
|
412
|
+
|
|
413
|
+
for (const legacy of this.legacyServers) {
|
|
414
|
+
legacy.close();
|
|
415
|
+
}
|
|
416
|
+
this.legacyServers.clear();
|
|
417
|
+
|
|
418
|
+
if (this.server) {
|
|
419
|
+
this.server.close();
|
|
420
|
+
this.server = null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (existsSync(this.socketPath)) {
|
|
424
|
+
try {
|
|
425
|
+
unlinkSync(this.socketPath);
|
|
426
|
+
} catch {
|
|
427
|
+
// Ignore
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Get the socket path (for diagnostics). */
|
|
433
|
+
getSocketPath(): string {
|
|
434
|
+
return this.socketPath;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Re-bind the listening socket if its path entry is missing on disk.
|
|
439
|
+
*
|
|
440
|
+
* Public for tests so the watchdog can be exercised deterministically
|
|
441
|
+
* without waiting for the interval. Returns `true` when a re-bind was
|
|
442
|
+
* performed, `false` otherwise.
|
|
443
|
+
*/
|
|
444
|
+
async rebindIfMissing(): Promise<boolean> {
|
|
445
|
+
return this.watchdog.rebindIfMissing();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ── Internal ──────────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
private createListeningServer(): Server {
|
|
451
|
+
const server = createServer((socket) => {
|
|
363
452
|
this.clients.add(socket);
|
|
364
453
|
const connection = new SkillIpcConnectionState(
|
|
365
454
|
`skill-ipc-${this.nextConnectionId++}`,
|
|
@@ -406,45 +495,13 @@ export class SkillIpcServer {
|
|
|
406
495
|
});
|
|
407
496
|
});
|
|
408
497
|
|
|
409
|
-
|
|
498
|
+
server.on("error", (err) => {
|
|
410
499
|
log.error({ err }, "Skill IPC server error");
|
|
411
500
|
});
|
|
412
501
|
|
|
413
|
-
|
|
414
|
-
log.info({ path: this.socketPath }, "Skill IPC server listening");
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/** Stop the server and disconnect all clients. */
|
|
419
|
-
stop(): void {
|
|
420
|
-
for (const client of this.clients) {
|
|
421
|
-
this.teardownSubscriptions(client);
|
|
422
|
-
this.teardownConnection(client);
|
|
423
|
-
if (!client.destroyed) client.destroy();
|
|
424
|
-
}
|
|
425
|
-
this.clients.clear();
|
|
426
|
-
|
|
427
|
-
if (this.server) {
|
|
428
|
-
this.server.close();
|
|
429
|
-
this.server = null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (existsSync(this.socketPath)) {
|
|
433
|
-
try {
|
|
434
|
-
unlinkSync(this.socketPath);
|
|
435
|
-
} catch {
|
|
436
|
-
// Ignore
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/** Get the socket path (for diagnostics). */
|
|
442
|
-
getSocketPath(): string {
|
|
443
|
-
return this.socketPath;
|
|
502
|
+
return server;
|
|
444
503
|
}
|
|
445
504
|
|
|
446
|
-
// ── Internal ──────────────────────────────────────────────────────────
|
|
447
|
-
|
|
448
505
|
private handleMessage(socket: Socket, line: string): void {
|
|
449
506
|
let frame: IpcRequest & { result?: unknown; error?: string };
|
|
450
507
|
try {
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for v1/v2 mutual exclusion in `maybeEnqueueGraphMaintenanceJobs`.
|
|
3
3
|
*
|
|
4
|
-
* The schedule is
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The schedule is mutually exclusive: when `memory.v2.enabled` is true,
|
|
5
|
+
* only `memory_v2_consolidate` is scheduled; otherwise the four v1
|
|
6
|
+
* entries (decay, consolidate, pattern_scan, narrative) fire and the v2
|
|
7
|
+
* entry does not.
|
|
8
8
|
*
|
|
9
9
|
* Coverage:
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* yet elapsed).
|
|
15
|
-
* - Flag on + config on, stale checkpoint → v2 row enqueued, checkpoint
|
|
16
|
-
* refreshed.
|
|
10
|
+
* - Config off → only v1 entries fire (no `memory_v2_consolidate`).
|
|
11
|
+
* - Config on, no prior checkpoint → only the v2 entry fires.
|
|
12
|
+
* - Config on, recent checkpoint → no v2 row (interval not yet elapsed).
|
|
13
|
+
* - Config on, stale checkpoint → v2 row enqueued, checkpoint refreshed.
|
|
17
14
|
*
|
|
18
15
|
* The sweep job is intentionally NOT scheduled here: it is wired into the
|
|
19
16
|
* `graph_extract` debounce in `indexer.ts`. Those triggers are covered by
|
|
@@ -27,7 +24,6 @@ import { tmpdir } from "node:os";
|
|
|
27
24
|
import { join } from "node:path";
|
|
28
25
|
import {
|
|
29
26
|
afterAll,
|
|
30
|
-
afterEach,
|
|
31
27
|
beforeAll,
|
|
32
28
|
beforeEach,
|
|
33
29
|
describe,
|
|
@@ -69,8 +65,6 @@ const { getDb } = await import("../db-connection.js");
|
|
|
69
65
|
const { initializeDb } = await import("../db-init.js");
|
|
70
66
|
const { resetTestTables } = await import("../raw-query.js");
|
|
71
67
|
const { memoryJobs } = await import("../schema.js");
|
|
72
|
-
const { _setOverridesForTesting } =
|
|
73
|
-
await import("../../config/assistant-feature-flags.js");
|
|
74
68
|
const { applyNestedDefaults } = await import("../../config/loader.js");
|
|
75
69
|
const { setMemoryCheckpoint, deleteMemoryCheckpoint } =
|
|
76
70
|
await import("../checkpoints.js");
|
|
@@ -111,26 +105,10 @@ beforeEach(() => {
|
|
|
111
105
|
resetTestTables("memory_jobs", "memory_checkpoints");
|
|
112
106
|
});
|
|
113
107
|
|
|
114
|
-
afterEach(() => {
|
|
115
|
-
_setOverridesForTesting({});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
108
|
// ---------------------------------------------------------------------------
|
|
119
109
|
|
|
120
110
|
describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
|
|
121
|
-
test("does not enqueue consolidate when memory-v2-enabled flag is off", () => {
|
|
122
|
-
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
123
|
-
const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
|
|
124
|
-
|
|
125
|
-
// Force the interval to look elapsed. If the gate failed open, this
|
|
126
|
-
// would be enough to enqueue a job.
|
|
127
|
-
maybeEnqueueGraphMaintenanceJobs(config, Date.now());
|
|
128
|
-
|
|
129
|
-
expect(countPendingJobs("memory_v2_consolidate")).toBe(0);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
111
|
test("does not enqueue consolidate when config.memory.v2.enabled is off", () => {
|
|
133
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
134
112
|
const config = buildConfig({ v2Enabled: false, intervalHours: 1 });
|
|
135
113
|
|
|
136
114
|
maybeEnqueueGraphMaintenanceJobs(config, Date.now());
|
|
@@ -138,8 +116,7 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
|
|
|
138
116
|
expect(countPendingJobs("memory_v2_consolidate")).toBe(0);
|
|
139
117
|
});
|
|
140
118
|
|
|
141
|
-
test("enqueues consolidate when
|
|
142
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
119
|
+
test("enqueues consolidate when v2 is on and no checkpoint exists", () => {
|
|
143
120
|
const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
|
|
144
121
|
|
|
145
122
|
maybeEnqueueGraphMaintenanceJobs(config, Date.now());
|
|
@@ -153,7 +130,6 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
|
|
|
153
130
|
});
|
|
154
131
|
|
|
155
132
|
test("does not enqueue consolidate before the interval has elapsed", () => {
|
|
156
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
157
133
|
const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
|
|
158
134
|
|
|
159
135
|
const now = Date.now();
|
|
@@ -166,7 +142,6 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
|
|
|
166
142
|
});
|
|
167
143
|
|
|
168
144
|
test("enqueues consolidate again once the interval elapses", () => {
|
|
169
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
170
145
|
const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
|
|
171
146
|
|
|
172
147
|
const now = Date.now();
|
|
@@ -182,7 +157,6 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
|
|
|
182
157
|
});
|
|
183
158
|
|
|
184
159
|
test("respects a custom consolidation_interval_hours value", () => {
|
|
185
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
186
160
|
const config = buildConfig({ v2Enabled: true, intervalHours: 6 });
|
|
187
161
|
|
|
188
162
|
const now = Date.now();
|
|
@@ -206,7 +180,6 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
|
|
|
206
180
|
});
|
|
207
181
|
|
|
208
182
|
test("v1 maintenance entries are suppressed when v2 is active", () => {
|
|
209
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
210
183
|
const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
|
|
211
184
|
|
|
212
185
|
// No checkpoints set — every entry would be due if it were scheduled.
|
|
@@ -225,27 +198,7 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
|
|
|
225
198
|
expect(countPendingJobs("memory_v2_consolidate")).toBe(1);
|
|
226
199
|
});
|
|
227
200
|
|
|
228
|
-
test("
|
|
229
|
-
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
230
|
-
const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
|
|
231
|
-
|
|
232
|
-
deleteMemoryCheckpoint("graph_maintenance:decay:last_run");
|
|
233
|
-
deleteMemoryCheckpoint("graph_maintenance:consolidate:last_run");
|
|
234
|
-
deleteMemoryCheckpoint("graph_maintenance:pattern_scan:last_run");
|
|
235
|
-
deleteMemoryCheckpoint("graph_maintenance:narrative:last_run");
|
|
236
|
-
deleteMemoryCheckpoint(CONSOLIDATE_CHECKPOINT_KEY);
|
|
237
|
-
|
|
238
|
-
maybeEnqueueGraphMaintenanceJobs(config, Date.now());
|
|
239
|
-
|
|
240
|
-
expect(countPendingJobs("graph_decay")).toBe(1);
|
|
241
|
-
expect(countPendingJobs("graph_consolidate")).toBe(1);
|
|
242
|
-
expect(countPendingJobs("graph_pattern_scan")).toBe(1);
|
|
243
|
-
expect(countPendingJobs("graph_narrative_refine")).toBe(1);
|
|
244
|
-
expect(countPendingJobs("memory_v2_consolidate")).toBe(0);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("config-gate-off path fires v1 entries and does not enqueue v2", () => {
|
|
248
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
201
|
+
test("v2-off path fires v1 entries and does not enqueue v2", () => {
|
|
249
202
|
const config = buildConfig({ v2Enabled: false, intervalHours: 1 });
|
|
250
203
|
|
|
251
204
|
deleteMemoryCheckpoint("graph_maintenance:decay:last_run");
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Memory v2 — `recall` adapter for the `memory` source
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
//
|
|
5
|
-
// When
|
|
5
|
+
// When v2 is enabled, the `memory` recall source reads from the v2
|
|
6
6
|
// concept-page subsystem (under `<workspace>/memory/concepts/`) instead of
|
|
7
7
|
// the legacy graph. Two retrieval paths run in parallel and merge:
|
|
8
8
|
//
|
|
@@ -26,8 +26,6 @@
|
|
|
26
26
|
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
27
27
|
import { extname, isAbsolute, join, relative } from "node:path";
|
|
28
28
|
|
|
29
|
-
import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
|
|
30
|
-
import type { AssistantConfig } from "../../../config/schema.js";
|
|
31
29
|
import { getLogger } from "../../../util/logger.js";
|
|
32
30
|
import { embedWithRetry } from "../../embed.js";
|
|
33
31
|
import { generateSparseEmbedding } from "../../embedding-backend.js";
|
|
@@ -46,20 +44,6 @@ import type {
|
|
|
46
44
|
RecallSearchResult,
|
|
47
45
|
} from "../types.js";
|
|
48
46
|
|
|
49
|
-
/**
|
|
50
|
-
* True when both v2 gates are on. Single source of truth for whether v2 is
|
|
51
|
-
* the active memory subsystem — used by recall sources (`memory`, `pkb`),
|
|
52
|
-
* per-turn injectors, the indexer's v1 graph_extract suppression, and the
|
|
53
|
-
* v1/v2 maintenance scheduler. The historical name retains "Read" but the
|
|
54
|
-
* predicate now gates both read and write paths.
|
|
55
|
-
*/
|
|
56
|
-
export function isMemoryV2ReadActive(config: AssistantConfig): boolean {
|
|
57
|
-
return (
|
|
58
|
-
isAssistantFeatureFlagEnabled("memory-v2-enabled", config) &&
|
|
59
|
-
config.memory.v2.enabled
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
47
|
const log = getLogger("context-search-memory-v2-source");
|
|
64
48
|
|
|
65
49
|
/**
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
RecallSearchContext,
|
|
10
10
|
RecallSearchResult,
|
|
11
11
|
} from "../types.js";
|
|
12
|
-
import {
|
|
12
|
+
import { searchMemoryV2Source } from "./memory-v2.js";
|
|
13
13
|
|
|
14
14
|
const log = getLogger("context-search-memory-source");
|
|
15
15
|
|
|
@@ -23,7 +23,7 @@ export async function searchMemorySource(
|
|
|
23
23
|
return { evidence: [] };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
if (
|
|
26
|
+
if (context.config.memory.v2.enabled) {
|
|
27
27
|
return searchMemoryV2Source(query, context, normalizedLimit);
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
RecallSearchContext,
|
|
16
16
|
RecallSearchResult,
|
|
17
17
|
} from "../types.js";
|
|
18
|
-
import { isMemoryV2ReadActive } from "./memory-v2.js";
|
|
19
18
|
|
|
20
19
|
const log = getLogger("context-search-pkb-source");
|
|
21
20
|
|
|
@@ -76,7 +75,7 @@ export async function searchPkbSource(
|
|
|
76
75
|
context: RecallSearchContext,
|
|
77
76
|
limit: number,
|
|
78
77
|
): Promise<RecallSearchResult> {
|
|
79
|
-
if (
|
|
78
|
+
if (context.config.memory.v2.enabled) {
|
|
80
79
|
return { evidence: [] };
|
|
81
80
|
}
|
|
82
81
|
|
|
@@ -139,7 +138,7 @@ export async function searchPkbSource(
|
|
|
139
138
|
export function readPkbContextEvidence(
|
|
140
139
|
context: RecallSearchContext,
|
|
141
140
|
): RecallEvidence[] {
|
|
142
|
-
if (
|
|
141
|
+
if (context.config.memory.v2.enabled) {
|
|
143
142
|
return [];
|
|
144
143
|
}
|
|
145
144
|
|