@vellumai/assistant 0.4.49 → 0.4.50
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 +24 -33
- package/README.md +3 -3
- package/docs/architecture/memory.md +180 -119
- package/package.json +2 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -0
- package/src/__tests__/credential-vault.test.ts +13 -1
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/invite-redemption-service.test.ts +65 -1
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +572 -5
- package/src/__tests__/oauth-store.test.ts +120 -6
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +46 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secure-keys.test.ts +7 -2
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/voice-invite-redemption.test.ts +32 -1
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +132 -0
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +43 -5
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +1 -1
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/doctor.ts +4 -3
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +31 -2
- package/src/cli/commands/oauth/connections.ts +431 -97
- package/src/cli/commands/oauth/providers.ts +15 -1
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +4 -10
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +61 -3
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +31 -3
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -0
- package/src/daemon/server.ts +12 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/instrument.ts +61 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +22 -3
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/oauth/byo-connection.test.ts +8 -1
- package/src/oauth/oauth-store.ts +113 -27
- package/src/oauth/seed-providers.ts +6 -0
- package/src/oauth/token-persistence.ts +11 -3
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +19 -1
- package/src/runtime/invite-service.ts +25 -0
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/conversation-routes.ts +9 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +2 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +2 -1
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/secure-keys.ts +25 -3
- package/src/security/token-manager.ts +36 -36
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +1 -3
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/schedule/create.ts +8 -1
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -2,26 +2,15 @@ import { and, asc, eq, inArray, lt } from "drizzle-orm";
|
|
|
2
2
|
|
|
3
3
|
import type { AssistantConfig } from "../../config/types.js";
|
|
4
4
|
import { getLogger } from "../../util/logger.js";
|
|
5
|
-
import { checkContradictions } from "../contradiction-checker.js";
|
|
6
5
|
import { getDb, rawAll, rawRun } from "../db.js";
|
|
7
|
-
import { asPositiveMs
|
|
6
|
+
import { asPositiveMs } from "../job-utils.js";
|
|
8
7
|
import { enqueueMemoryJob, type MemoryJob } from "../jobs-store.js";
|
|
9
|
-
import {
|
|
10
|
-
memoryEmbeddings,
|
|
11
|
-
memoryItemEntities,
|
|
12
|
-
memoryItems,
|
|
13
|
-
} from "../schema.js";
|
|
8
|
+
import { memoryEmbeddings, memoryItems } from "../schema.js";
|
|
14
9
|
|
|
15
10
|
const log = getLogger("memory-jobs-worker");
|
|
16
11
|
|
|
17
12
|
const CLEANUP_BATCH_LIMIT = 250;
|
|
18
13
|
|
|
19
|
-
export async function checkContradictionsJob(job: MemoryJob): Promise<void> {
|
|
20
|
-
const itemId = asString(job.payload.itemId);
|
|
21
|
-
if (!itemId) return;
|
|
22
|
-
await checkContradictions(itemId);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
14
|
export function cleanupStaleSupersededItemsJob(
|
|
26
15
|
job: MemoryJob,
|
|
27
16
|
config: AssistantConfig,
|
|
@@ -46,9 +35,6 @@ export function cleanupStaleSupersededItemsJob(
|
|
|
46
35
|
if (stale.length === 0) return;
|
|
47
36
|
|
|
48
37
|
const ids = stale.map((row) => row.id);
|
|
49
|
-
db.delete(memoryItemEntities)
|
|
50
|
-
.where(inArray(memoryItemEntities.memoryItemId, ids))
|
|
51
|
-
.run();
|
|
52
38
|
db.delete(memoryEmbeddings)
|
|
53
39
|
.where(
|
|
54
40
|
and(
|
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
import { eq } from "drizzle-orm";
|
|
2
2
|
|
|
3
|
-
import { getConfig } from "../../config/loader.js";
|
|
4
|
-
import type { AssistantConfig } from "../../config/types.js";
|
|
5
3
|
import { getLogger } from "../../util/logger.js";
|
|
6
4
|
import { getDb } from "../db.js";
|
|
7
|
-
import {
|
|
8
|
-
extractEntitiesWithLLM,
|
|
9
|
-
linkMemoryItemToEntity,
|
|
10
|
-
resolveEntityName,
|
|
11
|
-
upsertEntity,
|
|
12
|
-
upsertEntityRelation,
|
|
13
|
-
} from "../entity-extractor.js";
|
|
14
5
|
import { extractAndUpsertMemoryItemsForMessage } from "../items-extractor.js";
|
|
15
6
|
import { asString } from "../job-utils.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { memoryItemSources, messages } from "../schema.js";
|
|
7
|
+
import type { MemoryJob } from "../jobs-store.js";
|
|
8
|
+
import { messages } from "../schema.js";
|
|
19
9
|
import { isConversationFailed } from "../task-memory-cleanup.js";
|
|
20
10
|
|
|
21
11
|
const log = getLogger("memory-jobs-worker");
|
|
@@ -47,130 +37,4 @@ export async function extractItemsJob(job: MemoryJob): Promise<void> {
|
|
|
47
37
|
scopeId,
|
|
48
38
|
msg?.conversationId,
|
|
49
39
|
);
|
|
50
|
-
// Queue entity extraction for this message after items are extracted
|
|
51
|
-
const config = getConfig();
|
|
52
|
-
if (config.memory.entity.enabled) {
|
|
53
|
-
enqueueMemoryJob("extract_entities", { messageId, scopeId });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function extractEntitiesJob(
|
|
58
|
-
job: MemoryJob,
|
|
59
|
-
config: AssistantConfig,
|
|
60
|
-
): Promise<void> {
|
|
61
|
-
const messageId = asString(job.payload.messageId);
|
|
62
|
-
if (!messageId) return;
|
|
63
|
-
|
|
64
|
-
const db = getDb();
|
|
65
|
-
|
|
66
|
-
// Guard: skip entity extraction for failed conversations. Entity extraction
|
|
67
|
-
// jobs are enqueued by extractItemsJob after items are extracted; while new
|
|
68
|
-
// jobs won't be queued (extractItemsJob returns early for failed convos),
|
|
69
|
-
// any entity jobs enqueued before the failure marker was set must be caught.
|
|
70
|
-
const msgRow = db
|
|
71
|
-
.select({ conversationId: messages.conversationId })
|
|
72
|
-
.from(messages)
|
|
73
|
-
.where(eq(messages.id, messageId))
|
|
74
|
-
.get();
|
|
75
|
-
if (msgRow && isConversationFailed(msgRow.conversationId)) {
|
|
76
|
-
log.info(
|
|
77
|
-
{ messageId, conversationId: msgRow.conversationId },
|
|
78
|
-
"Skipping entity extraction for failed conversation",
|
|
79
|
-
);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const message = db
|
|
84
|
-
.select({
|
|
85
|
-
id: messages.id,
|
|
86
|
-
content: messages.content,
|
|
87
|
-
})
|
|
88
|
-
.from(messages)
|
|
89
|
-
.where(eq(messages.id, messageId))
|
|
90
|
-
.get();
|
|
91
|
-
if (!message) return;
|
|
92
|
-
|
|
93
|
-
const text = extractTextFromStoredMessageContent(message.content);
|
|
94
|
-
if (text.trim().length < 15) return;
|
|
95
|
-
|
|
96
|
-
const extracted = await extractEntitiesWithLLM(text, config.memory.entity);
|
|
97
|
-
const entities = extracted.entities;
|
|
98
|
-
const relations = extracted.relations;
|
|
99
|
-
if (entities.length === 0 && relations.length === 0) return;
|
|
100
|
-
|
|
101
|
-
// Find all memory items linked to this message via memory_item_sources
|
|
102
|
-
const linkedItems = db
|
|
103
|
-
.select({ memoryItemId: memoryItemSources.memoryItemId })
|
|
104
|
-
.from(memoryItemSources)
|
|
105
|
-
.where(eq(memoryItemSources.messageId, messageId))
|
|
106
|
-
.all();
|
|
107
|
-
const itemIds = linkedItems.map((row) => row.memoryItemId);
|
|
108
|
-
const entityNameToId = new Map<string, string>();
|
|
109
|
-
|
|
110
|
-
for (const entity of entities) {
|
|
111
|
-
const entityId = upsertEntity(entity);
|
|
112
|
-
entityNameToId.set(entity.name.toLowerCase(), entityId);
|
|
113
|
-
for (const alias of entity.aliases) {
|
|
114
|
-
entityNameToId.set(alias.toLowerCase(), entityId);
|
|
115
|
-
}
|
|
116
|
-
// Link all memory items from this message to the entity
|
|
117
|
-
for (const itemId of itemIds) {
|
|
118
|
-
linkMemoryItemToEntity(itemId, entityId);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const relationTelemetry = {
|
|
123
|
-
attempted: 0,
|
|
124
|
-
parsed: relations.length,
|
|
125
|
-
persisted: 0,
|
|
126
|
-
dropped: 0,
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
if (config.memory.entity.extractRelations.enabled && relations.length > 0) {
|
|
130
|
-
const seenRelationKeys = new Set<string>();
|
|
131
|
-
for (const relation of relations) {
|
|
132
|
-
relationTelemetry.attempted += 1;
|
|
133
|
-
const sourceLookup = relation.sourceEntityName.toLowerCase();
|
|
134
|
-
const targetLookup = relation.targetEntityName.toLowerCase();
|
|
135
|
-
const sourceEntityId =
|
|
136
|
-
entityNameToId.get(sourceLookup) ??
|
|
137
|
-
resolveEntityName(relation.sourceEntityName);
|
|
138
|
-
const targetEntityId =
|
|
139
|
-
entityNameToId.get(targetLookup) ??
|
|
140
|
-
resolveEntityName(relation.targetEntityName);
|
|
141
|
-
if (
|
|
142
|
-
!sourceEntityId ||
|
|
143
|
-
!targetEntityId ||
|
|
144
|
-
sourceEntityId === targetEntityId
|
|
145
|
-
) {
|
|
146
|
-
relationTelemetry.dropped += 1;
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const dedupeKey = `${sourceEntityId}|${targetEntityId}|${relation.relation}`;
|
|
151
|
-
if (seenRelationKeys.has(dedupeKey)) continue;
|
|
152
|
-
seenRelationKeys.add(dedupeKey);
|
|
153
|
-
|
|
154
|
-
upsertEntityRelation({
|
|
155
|
-
sourceEntityId,
|
|
156
|
-
targetEntityId,
|
|
157
|
-
relation: relation.relation,
|
|
158
|
-
evidence: relation.evidence,
|
|
159
|
-
});
|
|
160
|
-
relationTelemetry.persisted += 1;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
log.debug(
|
|
165
|
-
{
|
|
166
|
-
messageId,
|
|
167
|
-
entityCount: entities.length,
|
|
168
|
-
linkedItems: itemIds.length,
|
|
169
|
-
relationAttempts: relationTelemetry.attempted,
|
|
170
|
-
relationParsed: relationTelemetry.parsed,
|
|
171
|
-
relationPersisted: relationTelemetry.persisted,
|
|
172
|
-
relationDropped: relationTelemetry.dropped,
|
|
173
|
-
},
|
|
174
|
-
"Extracted entity graph from message",
|
|
175
|
-
);
|
|
176
40
|
}
|
|
@@ -2,7 +2,7 @@ import { eq, like } from "drizzle-orm";
|
|
|
2
2
|
|
|
3
3
|
import { getConfig } from "../../config/loader.js";
|
|
4
4
|
import { getLogger } from "../../util/logger.js";
|
|
5
|
-
import { getDb
|
|
5
|
+
import { getDb } from "../db.js";
|
|
6
6
|
import { selectedBackendSupportsMultimodal } from "../embedding-backend.js";
|
|
7
7
|
import { asString, BackendUnavailableError } from "../job-utils.js";
|
|
8
8
|
import { enqueueMemoryJob, type MemoryJob } from "../jobs-store.js";
|
|
@@ -22,11 +22,6 @@ const log = getLogger("memory-jobs-worker");
|
|
|
22
22
|
|
|
23
23
|
export function rebuildIndexJob(): void {
|
|
24
24
|
const db = getDb();
|
|
25
|
-
rawExec(/*sql*/ `DELETE FROM memory_segment_fts`);
|
|
26
|
-
rawExec(/*sql*/ `
|
|
27
|
-
INSERT INTO memory_segment_fts(segment_id, text)
|
|
28
|
-
SELECT id, text FROM memory_segments
|
|
29
|
-
`);
|
|
30
25
|
db.delete(memoryEmbeddings).run();
|
|
31
26
|
|
|
32
27
|
const items = db
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, desc, eq,
|
|
1
|
+
import { and, desc, eq, sql } from "drizzle-orm";
|
|
2
2
|
import { v4 as uuid } from "uuid";
|
|
3
3
|
|
|
4
4
|
import type { AssistantConfig } from "../../config/types.js";
|
|
@@ -12,14 +12,9 @@ import {
|
|
|
12
12
|
import { getLogger } from "../../util/logger.js";
|
|
13
13
|
import { getConversationMemoryScopeId } from "../conversation-crud.js";
|
|
14
14
|
import { getDb } from "../db.js";
|
|
15
|
-
import {
|
|
16
|
-
asString,
|
|
17
|
-
currentMonthWindow,
|
|
18
|
-
currentWeekWindow,
|
|
19
|
-
truncate,
|
|
20
|
-
} from "../job-utils.js";
|
|
15
|
+
import { asString, truncate } from "../job-utils.js";
|
|
21
16
|
import { enqueueMemoryJob, type MemoryJob } from "../jobs-store.js";
|
|
22
|
-
import {
|
|
17
|
+
import { memorySegments, memorySummaries } from "../schema.js";
|
|
23
18
|
|
|
24
19
|
const log = getLogger("memory-jobs-worker");
|
|
25
20
|
|
|
@@ -38,18 +33,6 @@ const CONVERSATION_SUMMARY_SYSTEM_PROMPT = [
|
|
|
38
33
|
"- If updating an existing summary with new data, merge new information and remove anything that was superseded.",
|
|
39
34
|
].join("\n");
|
|
40
35
|
|
|
41
|
-
const GLOBAL_SUMMARY_SYSTEM_PROMPT = [
|
|
42
|
-
"You are a memory summarization system. Your job is to synthesize a higher-level summary from multiple conversation summaries and memory items.",
|
|
43
|
-
"",
|
|
44
|
-
"Guidelines:",
|
|
45
|
-
"- Identify recurring themes, cross-cutting decisions, and persistent user preferences.",
|
|
46
|
-
"- Highlight the most important facts, active projects, and ongoing concerns.",
|
|
47
|
-
"- De-duplicate information that appears across multiple conversations.",
|
|
48
|
-
"- Use concise sections with bullet points.",
|
|
49
|
-
"- Target 400-600 tokens. Be dense but readable.",
|
|
50
|
-
"- If updating an existing summary with new data, merge new information and remove anything that was superseded.",
|
|
51
|
-
].join("\n");
|
|
52
|
-
|
|
53
36
|
export async function buildConversationSummaryJob(
|
|
54
37
|
job: MemoryJob,
|
|
55
38
|
config: AssistantConfig,
|
|
@@ -144,134 +127,6 @@ export async function buildConversationSummaryJob(
|
|
|
144
127
|
}
|
|
145
128
|
}
|
|
146
129
|
|
|
147
|
-
export async function buildGlobalSummaryJob(
|
|
148
|
-
scope: "weekly_global" | "monthly_global",
|
|
149
|
-
config: AssistantConfig,
|
|
150
|
-
): Promise<void> {
|
|
151
|
-
const db = getDb();
|
|
152
|
-
const now = new Date();
|
|
153
|
-
const { startMs, endMs, scopeKey } =
|
|
154
|
-
scope === "weekly_global"
|
|
155
|
-
? currentWeekWindow(now)
|
|
156
|
-
: currentMonthWindow(now);
|
|
157
|
-
|
|
158
|
-
const items = db
|
|
159
|
-
.select()
|
|
160
|
-
.from(memoryItems)
|
|
161
|
-
.where(
|
|
162
|
-
and(
|
|
163
|
-
eq(memoryItems.status, "active"),
|
|
164
|
-
isNull(memoryItems.invalidAt),
|
|
165
|
-
eq(memoryItems.scopeId, "default"),
|
|
166
|
-
gte(memoryItems.lastSeenAt, startMs),
|
|
167
|
-
lt(memoryItems.lastSeenAt, endMs),
|
|
168
|
-
),
|
|
169
|
-
)
|
|
170
|
-
.orderBy(desc(memoryItems.lastSeenAt))
|
|
171
|
-
.limit(80)
|
|
172
|
-
.all();
|
|
173
|
-
|
|
174
|
-
// Gather conversation summaries from this period for higher-level synthesis
|
|
175
|
-
const convSummaries = db
|
|
176
|
-
.select()
|
|
177
|
-
.from(memorySummaries)
|
|
178
|
-
.where(
|
|
179
|
-
and(
|
|
180
|
-
eq(memorySummaries.scope, "conversation"),
|
|
181
|
-
eq(memorySummaries.scopeId, "default"),
|
|
182
|
-
gte(memorySummaries.endAt, startMs),
|
|
183
|
-
lt(memorySummaries.startAt, endMs),
|
|
184
|
-
),
|
|
185
|
-
)
|
|
186
|
-
.orderBy(desc(memorySummaries.endAt))
|
|
187
|
-
.limit(20)
|
|
188
|
-
.all();
|
|
189
|
-
|
|
190
|
-
if (items.length === 0 && convSummaries.length === 0) return;
|
|
191
|
-
|
|
192
|
-
// Build input for LLM: conversation summaries + active items
|
|
193
|
-
const parts: string[] = [];
|
|
194
|
-
if (convSummaries.length > 0) {
|
|
195
|
-
parts.push("## Conversation Summaries");
|
|
196
|
-
for (const cs of convSummaries) {
|
|
197
|
-
parts.push(`### ${cs.scopeKey}\n${truncate(cs.summary, 600)}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (items.length > 0) {
|
|
201
|
-
parts.push("## Active Memory Items");
|
|
202
|
-
for (const item of items.slice(0, 40)) {
|
|
203
|
-
parts.push(
|
|
204
|
-
`- [${item.kind}] ${item.subject}: ${truncate(item.statement, 180)}`,
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
const inputText = parts.join("\n\n");
|
|
209
|
-
|
|
210
|
-
const existing = db
|
|
211
|
-
.select()
|
|
212
|
-
.from(memorySummaries)
|
|
213
|
-
.where(
|
|
214
|
-
and(
|
|
215
|
-
eq(memorySummaries.scope, scope),
|
|
216
|
-
eq(memorySummaries.scopeKey, scopeKey),
|
|
217
|
-
),
|
|
218
|
-
)
|
|
219
|
-
.get();
|
|
220
|
-
|
|
221
|
-
const label = scope === "weekly_global" ? "weekly" : "monthly";
|
|
222
|
-
const summaryText = await summarizeWithLLM(
|
|
223
|
-
config,
|
|
224
|
-
GLOBAL_SUMMARY_SYSTEM_PROMPT,
|
|
225
|
-
existing?.summary ?? null,
|
|
226
|
-
inputText,
|
|
227
|
-
label,
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
const ts = Date.now();
|
|
231
|
-
const summaryId = existing?.id ?? uuid();
|
|
232
|
-
db.insert(memorySummaries)
|
|
233
|
-
.values({
|
|
234
|
-
id: summaryId,
|
|
235
|
-
scope,
|
|
236
|
-
scopeKey,
|
|
237
|
-
summary: summaryText,
|
|
238
|
-
tokenEstimate: estimateTextTokens(summaryText),
|
|
239
|
-
version: (existing?.version ?? 0) + 1,
|
|
240
|
-
startAt: startMs,
|
|
241
|
-
endAt: endMs,
|
|
242
|
-
createdAt: ts,
|
|
243
|
-
updatedAt: ts,
|
|
244
|
-
})
|
|
245
|
-
.onConflictDoUpdate({
|
|
246
|
-
target: [memorySummaries.scope, memorySummaries.scopeKey],
|
|
247
|
-
set: {
|
|
248
|
-
summary: summaryText,
|
|
249
|
-
tokenEstimate: estimateTextTokens(summaryText),
|
|
250
|
-
version: sql`${memorySummaries.version} + 1`,
|
|
251
|
-
startAt: startMs,
|
|
252
|
-
endAt: endMs,
|
|
253
|
-
updatedAt: ts,
|
|
254
|
-
},
|
|
255
|
-
})
|
|
256
|
-
.run();
|
|
257
|
-
|
|
258
|
-
// Re-query to get the actual persisted row ID — during a race the ON CONFLICT
|
|
259
|
-
// path keeps the winner's ID, not the pre-generated UUID from the loser.
|
|
260
|
-
const actualRow = db
|
|
261
|
-
.select({ id: memorySummaries.id })
|
|
262
|
-
.from(memorySummaries)
|
|
263
|
-
.where(
|
|
264
|
-
and(
|
|
265
|
-
eq(memorySummaries.scope, scope),
|
|
266
|
-
eq(memorySummaries.scopeKey, scopeKey),
|
|
267
|
-
),
|
|
268
|
-
)
|
|
269
|
-
.get();
|
|
270
|
-
if (actualRow) {
|
|
271
|
-
enqueueMemoryJob("embed_summary", { summaryId: actualRow.id });
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
130
|
async function summarizeWithLLM(
|
|
276
131
|
config: AssistantConfig,
|
|
277
132
|
systemPrompt: string,
|
package/src/memory/job-utils.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { getLogger } from "../util/logger.js";
|
|
|
8
8
|
import { getDb } from "./db.js";
|
|
9
9
|
import {
|
|
10
10
|
embedWithBackend,
|
|
11
|
+
generateSparseEmbedding,
|
|
11
12
|
getMemoryBackendStatus,
|
|
12
13
|
} from "./embedding-backend.js";
|
|
13
14
|
import type { EmbeddingInput } from "./embedding-types.js";
|
|
@@ -201,6 +202,14 @@ export async function embedAndUpsert(
|
|
|
201
202
|
? normalized.text
|
|
202
203
|
: `[${normalized.type}:${normalized.mimeType}]`;
|
|
203
204
|
|
|
205
|
+
// Generate sparse embedding from the same source text used for dense embedding.
|
|
206
|
+
// For non-text (media) inputs, sparse vectors are skipped since tokenization
|
|
207
|
+
// only applies to text content.
|
|
208
|
+
const sparseVector =
|
|
209
|
+
normalized.type === "text"
|
|
210
|
+
? generateSparseEmbedding(normalized.text)
|
|
211
|
+
: undefined;
|
|
212
|
+
|
|
204
213
|
// Persist embedding in SQLite for cross-restart cache
|
|
205
214
|
const now = Date.now();
|
|
206
215
|
try {
|
|
@@ -249,12 +258,18 @@ export async function embedAndUpsert(
|
|
|
249
258
|
try {
|
|
250
259
|
const modality = normalized.type;
|
|
251
260
|
await withQdrantBreaker(() =>
|
|
252
|
-
qdrant.upsert(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
261
|
+
qdrant.upsert(
|
|
262
|
+
targetType,
|
|
263
|
+
targetId,
|
|
264
|
+
vector,
|
|
265
|
+
{
|
|
266
|
+
text: payloadText,
|
|
267
|
+
modality,
|
|
268
|
+
created_at: (extraPayload?.created_at as number) ?? now,
|
|
269
|
+
...(extraPayload as Record<string, unknown> | undefined),
|
|
270
|
+
},
|
|
271
|
+
sparseVector,
|
|
272
|
+
),
|
|
258
273
|
);
|
|
259
274
|
} catch (err) {
|
|
260
275
|
log.warn(
|
|
@@ -265,56 +280,3 @@ export async function embedAndUpsert(
|
|
|
265
280
|
}
|
|
266
281
|
}
|
|
267
282
|
|
|
268
|
-
// ── Time window utilities ──────────────────────────────────────────
|
|
269
|
-
|
|
270
|
-
export function currentWeekWindow(now: Date): {
|
|
271
|
-
scopeKey: string;
|
|
272
|
-
startMs: number;
|
|
273
|
-
endMs: number;
|
|
274
|
-
} {
|
|
275
|
-
const day = (now.getUTCDay() + 6) % 7;
|
|
276
|
-
const start = new Date(
|
|
277
|
-
Date.UTC(
|
|
278
|
-
now.getUTCFullYear(),
|
|
279
|
-
now.getUTCMonth(),
|
|
280
|
-
now.getUTCDate() - day,
|
|
281
|
-
0,
|
|
282
|
-
0,
|
|
283
|
-
0,
|
|
284
|
-
0,
|
|
285
|
-
),
|
|
286
|
-
);
|
|
287
|
-
const end = new Date(start);
|
|
288
|
-
end.setUTCDate(end.getUTCDate() + 7);
|
|
289
|
-
const scopeKey = `${start.getUTCFullYear()}-W${weekNumber(start)
|
|
290
|
-
.toString()
|
|
291
|
-
.padStart(2, "0")}`;
|
|
292
|
-
return { scopeKey, startMs: start.getTime(), endMs: end.getTime() };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export function currentMonthWindow(now: Date): {
|
|
296
|
-
scopeKey: string;
|
|
297
|
-
startMs: number;
|
|
298
|
-
endMs: number;
|
|
299
|
-
} {
|
|
300
|
-
const start = new Date(
|
|
301
|
-
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1, 0, 0, 0, 0),
|
|
302
|
-
);
|
|
303
|
-
const end = new Date(
|
|
304
|
-
Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 0, 0, 0, 0),
|
|
305
|
-
);
|
|
306
|
-
const scopeKey = `${start.getUTCFullYear()}-${String(
|
|
307
|
-
start.getUTCMonth() + 1,
|
|
308
|
-
).padStart(2, "0")}`;
|
|
309
|
-
return { scopeKey, startMs: start.getTime(), endMs: end.getTime() };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function weekNumber(date: Date): number {
|
|
313
|
-
const tmp = new Date(
|
|
314
|
-
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()),
|
|
315
|
-
);
|
|
316
|
-
const dayNum = tmp.getUTCDay() || 7;
|
|
317
|
-
tmp.setUTCDate(tmp.getUTCDate() + 4 - dayNum);
|
|
318
|
-
const yearStart = new Date(Date.UTC(tmp.getUTCFullYear(), 0, 1));
|
|
319
|
-
return Math.ceil(((tmp.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
|
320
|
-
}
|
package/src/memory/jobs-store.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { v4 as uuid } from "uuid";
|
|
|
3
3
|
|
|
4
4
|
import { getLogger } from "../util/logger.js";
|
|
5
5
|
import { truncate } from "../util/truncate.js";
|
|
6
|
-
import { getDb, rawAll, rawChanges
|
|
6
|
+
import { getDb, rawAll, rawChanges } from "./db.js";
|
|
7
7
|
import { memoryJobs } from "./schema.js";
|
|
8
8
|
|
|
9
9
|
const log = getLogger("memory-jobs-store");
|
|
@@ -14,18 +14,14 @@ export type MemoryJobType =
|
|
|
14
14
|
| "embed_summary"
|
|
15
15
|
| "extract_items"
|
|
16
16
|
| "extract_entities"
|
|
17
|
-
| "resolve_pending_conflicts_for_message"
|
|
18
|
-
| "cleanup_resolved_conflicts"
|
|
19
17
|
| "cleanup_stale_superseded_items"
|
|
20
18
|
| "prune_old_conversations"
|
|
21
19
|
| "backfill_entity_relations"
|
|
22
|
-
| "check_contradictions"
|
|
23
20
|
| "refresh_weekly_summary"
|
|
24
21
|
| "refresh_monthly_summary"
|
|
25
22
|
| "build_conversation_summary"
|
|
26
23
|
| "backfill"
|
|
27
24
|
| "rebuild_index"
|
|
28
|
-
| "reconcile_fts"
|
|
29
25
|
| "delete_qdrant_vectors"
|
|
30
26
|
| "media_processing"
|
|
31
27
|
| "embed_media"
|
|
@@ -83,143 +79,6 @@ export function enqueueMemoryJob(
|
|
|
83
79
|
return id;
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
/**
|
|
87
|
-
* Ensure there is only one pending relation backfill orchestrator job.
|
|
88
|
-
* If `force=true` arrives while a pending job exists, its payload is upgraded.
|
|
89
|
-
*/
|
|
90
|
-
export function enqueueBackfillEntityRelationsJob(force = false): string {
|
|
91
|
-
const db = getDb();
|
|
92
|
-
const now = Date.now();
|
|
93
|
-
const existing = db
|
|
94
|
-
.select()
|
|
95
|
-
.from(memoryJobs)
|
|
96
|
-
.where(
|
|
97
|
-
and(
|
|
98
|
-
eq(memoryJobs.type, "backfill_entity_relations"),
|
|
99
|
-
eq(memoryJobs.status, "pending"),
|
|
100
|
-
),
|
|
101
|
-
)
|
|
102
|
-
.orderBy(asc(memoryJobs.createdAt))
|
|
103
|
-
.get();
|
|
104
|
-
|
|
105
|
-
if (existing) {
|
|
106
|
-
if (force) {
|
|
107
|
-
let payload: Record<string, unknown> = {};
|
|
108
|
-
try {
|
|
109
|
-
payload = JSON.parse(existing.payload) as Record<string, unknown>;
|
|
110
|
-
} catch {
|
|
111
|
-
payload = {};
|
|
112
|
-
}
|
|
113
|
-
if (payload.force !== true) {
|
|
114
|
-
db.update(memoryJobs)
|
|
115
|
-
.set({
|
|
116
|
-
payload: JSON.stringify({ ...payload, force: true }),
|
|
117
|
-
updatedAt: now,
|
|
118
|
-
})
|
|
119
|
-
.where(eq(memoryJobs.id, existing.id))
|
|
120
|
-
.run();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return existing.id;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return enqueueMemoryJob("backfill_entity_relations", { force });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function enqueueResolvePendingConflictsForMessageJob(
|
|
130
|
-
messageId: string,
|
|
131
|
-
scopeId = "default",
|
|
132
|
-
dbOverride?: Parameters<ReturnType<typeof getDb>["transaction"]>[0] extends (
|
|
133
|
-
tx: infer T,
|
|
134
|
-
) => unknown
|
|
135
|
-
? T
|
|
136
|
-
: never,
|
|
137
|
-
): string {
|
|
138
|
-
const normalizedMessageId = messageId.trim();
|
|
139
|
-
if (!normalizedMessageId) {
|
|
140
|
-
throw new Error(
|
|
141
|
-
"enqueueResolvePendingConflictsForMessageJob requires a non-empty messageId",
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
const normalizedScopeId = scopeId.trim() || "default";
|
|
145
|
-
// Dedup check always uses root db since tx doesn't expose raw client
|
|
146
|
-
const existing = rawGet<{ id: string }>(
|
|
147
|
-
`
|
|
148
|
-
SELECT id
|
|
149
|
-
FROM memory_jobs
|
|
150
|
-
WHERE type = 'resolve_pending_conflicts_for_message'
|
|
151
|
-
AND status IN ('pending', 'running')
|
|
152
|
-
AND json_extract(payload, '$.messageId') = ?
|
|
153
|
-
AND COALESCE(json_extract(payload, '$.scopeId'), 'default') = ?
|
|
154
|
-
ORDER BY created_at ASC
|
|
155
|
-
LIMIT 1
|
|
156
|
-
`,
|
|
157
|
-
normalizedMessageId,
|
|
158
|
-
normalizedScopeId,
|
|
159
|
-
);
|
|
160
|
-
if (existing?.id) return existing.id;
|
|
161
|
-
|
|
162
|
-
return enqueueMemoryJob(
|
|
163
|
-
"resolve_pending_conflicts_for_message",
|
|
164
|
-
{
|
|
165
|
-
messageId: normalizedMessageId,
|
|
166
|
-
scopeId: normalizedScopeId,
|
|
167
|
-
},
|
|
168
|
-
Date.now(),
|
|
169
|
-
dbOverride,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function enqueueCleanupResolvedConflictsJob(
|
|
174
|
-
retentionMs?: number,
|
|
175
|
-
): string {
|
|
176
|
-
const db = getDb();
|
|
177
|
-
const now = Date.now();
|
|
178
|
-
const existing = db
|
|
179
|
-
.select()
|
|
180
|
-
.from(memoryJobs)
|
|
181
|
-
.where(
|
|
182
|
-
and(
|
|
183
|
-
eq(memoryJobs.type, "cleanup_resolved_conflicts"),
|
|
184
|
-
inArray(memoryJobs.status, ["pending", "running"]),
|
|
185
|
-
),
|
|
186
|
-
)
|
|
187
|
-
.orderBy(asc(memoryJobs.createdAt))
|
|
188
|
-
.get();
|
|
189
|
-
if (existing) {
|
|
190
|
-
if (
|
|
191
|
-
existing.status === "pending" &&
|
|
192
|
-
typeof retentionMs === "number" &&
|
|
193
|
-
Number.isFinite(retentionMs) &&
|
|
194
|
-
retentionMs > 0
|
|
195
|
-
) {
|
|
196
|
-
let payload: Record<string, unknown> = {};
|
|
197
|
-
try {
|
|
198
|
-
payload = JSON.parse(existing.payload) as Record<string, unknown>;
|
|
199
|
-
} catch {
|
|
200
|
-
payload = {};
|
|
201
|
-
}
|
|
202
|
-
if (payload.retentionMs !== retentionMs) {
|
|
203
|
-
db.update(memoryJobs)
|
|
204
|
-
.set({
|
|
205
|
-
payload: JSON.stringify({ ...payload, retentionMs }),
|
|
206
|
-
updatedAt: now,
|
|
207
|
-
})
|
|
208
|
-
.where(eq(memoryJobs.id, existing.id))
|
|
209
|
-
.run();
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return existing.id;
|
|
213
|
-
}
|
|
214
|
-
const payload =
|
|
215
|
-
typeof retentionMs === "number" &&
|
|
216
|
-
Number.isFinite(retentionMs) &&
|
|
217
|
-
retentionMs > 0
|
|
218
|
-
? { retentionMs }
|
|
219
|
-
: {};
|
|
220
|
-
return enqueueMemoryJob("cleanup_resolved_conflicts", payload);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
82
|
export function enqueueCleanupStaleSupersededItemsJob(
|
|
224
83
|
retentionMs?: number,
|
|
225
84
|
): string {
|
|
@@ -319,23 +178,6 @@ export function enqueuePruneOldConversationsJob(
|
|
|
319
178
|
return enqueueMemoryJob("prune_old_conversations", payload);
|
|
320
179
|
}
|
|
321
180
|
|
|
322
|
-
export function enqueueReconcileFtsJob(): string {
|
|
323
|
-
const db = getDb();
|
|
324
|
-
const existing = db
|
|
325
|
-
.select()
|
|
326
|
-
.from(memoryJobs)
|
|
327
|
-
.where(
|
|
328
|
-
and(
|
|
329
|
-
eq(memoryJobs.type, "reconcile_fts"),
|
|
330
|
-
inArray(memoryJobs.status, ["pending", "running"]),
|
|
331
|
-
),
|
|
332
|
-
)
|
|
333
|
-
.orderBy(asc(memoryJobs.createdAt))
|
|
334
|
-
.get();
|
|
335
|
-
if (existing) return existing.id;
|
|
336
|
-
return enqueueMemoryJob("reconcile_fts", {});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
181
|
export function claimMemoryJobs(limit: number): MemoryJob[] {
|
|
340
182
|
if (limit <= 0) return [];
|
|
341
183
|
const db = getDb();
|